Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"websiteURL": "https://clone.is/you",
"defaultPrompt": [
"Set up Clone Loop for Codex.",
"Start a Clone Loop to keep working until tests pass.",
"Check my Clone API key status."
"Run a Clone Interview to clarify a feature before coding.",
"Start a Clone Loop to keep working until tests pass."
],
"brandColor": "#6F42C1"
}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .claude-plugin/plugin.json .codex-plugin/plugin.json hooks/stop-hook.mjs hooks/ask-user-question-hook.mjs README.md
git add .claude-plugin/plugin.json .codex-plugin/plugin.json hooks/stop-hook.mjs hooks/ask-user-question-hook.mjs scripts/predict-interview-answer.mjs README.md
git commit -m "chore: release ${TAG}"
git tag -a "$TAG" -m "clone ${VERSION}"
git push origin HEAD:main "$TAG"
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Open your agent and run:

```text
/clone:api-key status
/clone:interview "Clarify the feature before coding" --mode deep
/clone:loop "Run tests and fix any failures" --max-iterations 5
```

Expand All @@ -106,11 +107,28 @@ To update later: `claude plugin marketplace update clone-labs && claude plugin u

| Command | What it does |
|---|---|
| `/clone:interview "<topic>" [options]` | Clarify requirements into a local spec. |
| `/clone:loop "<task>" [options]` | Start a loop. |
| `/clone:cancel-loop` | Cancel the active loop. |
| `/clone:api-key status\|import-env\|set\|clear` | Manage your Clone API key. |
| `/clone:help` | Show command help. |

### Options for `/clone:interview`

- `--mode <quick|deep>` — interview depth. Default `deep`.
- `--max-questions <n>` — maximum questions before restating. Default `12`.
- `--output <path>` — project-local markdown spec path. Default
`.claude/clone-interview.local.md`.
- `--clone-threshold <n>` — confidence threshold for Clone-predicted
interview answers. Default `0.75`.
- `--no-auto-answer` — disable Clone-predicted answers and always ask you.

Clone Interview is the requirements side of Clone. It inspects repo facts,
asks human-judgment questions one at a time, asks Clone MCP to predict how you
would answer, and only auto-records the answer when confidence clears the
threshold. Low-confidence questions escalate to you. v1 is plugin-only for
question generation: Clone does not generate the interview questions yet.

### Options for `/clone:loop`

- `--max-iterations <n>` — stop after N iterations (`0` = unlimited).
Expand Down Expand Up @@ -247,9 +265,14 @@ existing config.
Then start a loop:

```text
clone-interview "Clarify the feature before coding" --mode deep
clone-loop "Run tests and fix any failures" --max-iterations 5
```

In Codex, Clone Interview uses the skill flow to run the same prediction script
before each user-facing interview question. In Claude Code, the bundled
AskUserQuestion hook can fill high-confidence answers automatically.

Codex plugin hooks require trust review. If Codex warns about untrusted hooks,
open `/hooks`, trust the Clone Loop plugin hooks, and retry.

Expand Down
17 changes: 17 additions & 0 deletions commands/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ predicted response so the user does not have to handle the popup manually.

## Available Commands

### /clone:interview

Clarify a vague requirement into a local Clone Interview spec before coding.

**Usage:**

```bash
/clone:interview "Add billing to the app"
/clone:interview "Improve onboarding" --mode quick --max-questions 5
/clone:interview "Build importer" --output docs/clone-interview/importer.md
```

Clone Interview is plugin-only in v1. It does not use Clone MCP as a question
generator. The agent inspects repo facts first, asks human-judgment questions
one at a time, and writes the working spec to
`.claude/clone-interview.local.md` unless `--output` is provided.

### /clone:loop

Start a Clone Loop in your current session.
Expand Down
22 changes: 22 additions & 0 deletions commands/interview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
description: "Clarify requirements into a Clone Interview spec"
argument-hint: "TOPIC [--max-questions N] [--mode quick|deep] [--output PATH]"
allowed-tools: Bash(node *setup-clone-interview.mjs*), AskUserQuestion
hide-from-slash-command-tool: "true"
---

# Clone Interview Command

Use the Bash tool to execute the Node setup script and initialize the Clone Interview:

```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/setup-clone-interview.mjs" $ARGUMENTS
```

If setup succeeds, run the interview in this session.

First inspect repository facts that are directly relevant to the topic. Auto-confirm only exact facts from files, and mark them as `[from-code][auto-confirmed]` in the working spec.

Ask all human-judgment questions through AskUserQuestion so Clone Interview can predict the user's answer first. When the prediction clears the configured threshold, the hook will fill in the answer automatically. When confidence is low, AskUserQuestion will reach the user normally.

Ask one question at a time. For free-form answers that include scope or constraints, structure the answer and confirm nothing was lost before recording it. Before closing, restate the one-sentence goal and get user confirmation, then update the spec markdown.
4 changes: 4 additions & 0 deletions hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ask-user-question-hook.mjs\""
},
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/interview-question-hook.mjs\""
}
]
}
Expand Down
95 changes: 95 additions & 0 deletions hooks/interview-question-hook.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env node

import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import { predictInterviewAnswer } from '../scripts/predict-interview-answer.mjs'

function readStdin() {
return new Promise((resolveRead) => {
let input = ''
process.stdin.setEncoding('utf8')
process.stdin.on('data', (chunk) => {
input += chunk
})
process.stdin.on('end', () => resolveRead(input))
})
}

function parseJson(input) {
const normalized = String(input || '').replace(/^\uFEFF/, '').trim()
return normalized ? JSON.parse(normalized) : {}
}

function allowAnswer({ toolInput, answers, confidence, threshold }) {
console.log(
JSON.stringify(
{
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'allow',
permissionDecisionReason: `Clone Interview answered with Clone-predicted response. Confidence ${confidence}; threshold ${threshold}.`,
updatedInput: {
...toolInput,
questions: toolInput.questions,
answers: {
...(toolInput.answers || {}),
...answers,
},
},
},
},
null,
2,
),
)
}

function answerKind(question) {
return Array.isArray(question?.options) && question.options.length ? 'choice' : 'freeform'
}

async function main() {
const hookInput = parseJson(await readStdin())
if (hookInput.tool_name && hookInput.tool_name !== 'AskUserQuestion') return

// Clone Loop owns AskUserQuestion while an active loop state exists.
if (existsSync(resolve(process.cwd(), '.claude', 'clone-loop.local.md'))) return

const toolInput = hookInput.tool_input || {}
const questions = Array.isArray(toolInput.questions) ? toolInput.questions : []
if (!questions.length) return

const answers = {}
const confidences = []
let threshold = 0.75

for (const question of questions) {
if (question?.multiSelect) return
const questionText = String(question?.question || '').trim()
if (!questionText) return

const result = await predictInterviewAnswer({
cwd: process.cwd(),
session_id: hookInput.session_id || '',
transcript_path: hookInput.transcript_path || '',
last_assistant_message: hookInput.last_assistant_message || '',
question: questionText,
options: Array.isArray(question.options) ? question.options : [],
answer_kind: answerKind(question),
})
if (result.decision !== 'auto' || !result.answer) return

answers[questionText] = result.answer
confidences.push(Number(result.confidence) || 0)
threshold = Number(result.threshold) || threshold
}

allowAnswer({
toolInput,
answers,
confidence: Math.min(...confidences),
threshold,
})
}

main().catch(() => {})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"test": "node --test tests/api-key-manager.test.mjs tests/setup-clone-loop.test.mjs tests/post-tool-use-capture.test.mjs tests/repo-identity.test.mjs tests/codex-plugin.test.mjs tests/codex-setup.test.mjs tests/codex-post-tool-use.test.mjs tests/codex-stop-hook.test.mjs tests/stop-hook-v2.test.mjs tests/ask-user-question-hook.test.mjs tests/release-automation.test.mjs",
"test": "node --test tests/api-key-manager.test.mjs tests/setup-clone-loop.test.mjs tests/clone-interview-setup.test.mjs tests/clone-interview-predict.test.mjs tests/clone-interview-question-hook.test.mjs tests/clone-interview-plugin.test.mjs tests/post-tool-use-capture.test.mjs tests/repo-identity.test.mjs tests/codex-plugin.test.mjs tests/codex-setup.test.mjs tests/codex-post-tool-use.test.mjs tests/codex-stop-hook.test.mjs tests/stop-hook-v2.test.mjs tests/ask-user-question-hook.test.mjs tests/release-automation.test.mjs",
"test:mcp:e2e": "node --test tests/remote-mcp-e2e.test.mjs"
}
}
1 change: 1 addition & 0 deletions scripts/bump-plugin-version.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,6 @@ if (existsSync(codexManifestPath)) {
}
updateClientVersion(join(options.root, 'hooks', 'stop-hook.mjs'), nextVersion)
updateClientVersion(join(options.root, 'hooks', 'ask-user-question-hook.mjs'), nextVersion)
updateClientVersion(join(options.root, 'scripts', 'predict-interview-answer.mjs'), nextVersion)

console.log(`clone plugin version: ${previousVersion} -> ${nextVersion}`)
Loading