claude-dev-env 2.5.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +20 -57
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -1
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +12 -2
- package/_shared/pr-loop/scripts/code_rules_gate_parts/baseline_import_isolation.py +309 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +540 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +206 -70
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/__init__.py +1 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/_repo_test_helpers.py +76 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_baseline_import_isolation.py +248 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_regression.py +309 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +91 -58
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +202 -0
- package/_shared/pr-loop/worker-spawn.md +3 -1
- package/agents/CLAUDE.md +1 -1
- package/agents/code-verifier.md +36 -7
- package/bin/codex-compat.mjs +104 -0
- package/bin/codex-compat.test.mjs +51 -0
- package/codex-capability-map.json +13 -0
- package/docs/CODE_RULES.md +2 -0
- package/docs/codex-compatibility.md +25 -0
- package/docs/nas-ssh-invocation.md +96 -12
- package/docs/references/code-review-enforcement.md +31 -6
- package/hooks/blocking/CLAUDE.md +3 -0
- package/hooks/blocking/config/code_review_enforcement_constants.py +40 -10
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +56 -3
- package/hooks/blocking/eli11_reply_enforcer.py +479 -0
- package/hooks/blocking/gh_body_arg_blocker.py +1 -1
- package/hooks/blocking/nas_ssh_binary_enforcer.py +8 -46
- package/hooks/blocking/shell_substitution_blocker.py +129 -0
- package/hooks/blocking/state_description_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +1 -1
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +2 -3
- package/hooks/blocking/test_eli11_reply_enforcer.py +457 -0
- package/hooks/blocking/test_shell_substitution_blocker.py +124 -0
- package/hooks/blocking/test_stop_dispatcher.py +23 -0
- package/hooks/blocking/test_unscoped_search_blocker.py +102 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +4 -8
- package/hooks/blocking/unscoped_search_blocker.py +391 -0
- package/hooks/git-hooks/CLAUDE.md +3 -0
- package/hooks/git-hooks/conftest.py +30 -0
- package/hooks/git-hooks/gate_utils.py +2 -2
- package/hooks/git-hooks/git_hooks_constants/__init__.py +41 -2
- package/hooks/git-hooks/pre_push.py +75 -4
- package/hooks/git-hooks/pre_push_base_reference.py +166 -0
- package/hooks/git-hooks/test_config.py +0 -15
- package/hooks/git-hooks/test_gate_utils.py +3 -15
- package/hooks/git-hooks/test_pre_commit.py +1 -15
- package/hooks/git-hooks/test_pre_push.py +236 -27
- package/hooks/git-hooks/test_pre_push_base_reference.py +339 -0
- package/hooks/hooks.json +0 -12
- package/hooks/hooks_constants/CLAUDE.md +5 -1
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +4 -4
- package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +101 -0
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +2 -8
- package/hooks/hooks_constants/shell_command_segments.py +82 -0
- package/hooks/hooks_constants/shell_substitution_blocker_constants.py +67 -0
- package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +5 -6
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/unscoped_search_blocker_constants.py +153 -0
- package/package.json +4 -2
- package/rules/CLAUDE.md +17 -23
- package/rules/agent-spawn-protocol.md +6 -6
- package/rules/anti-corollary-tests.md +1 -1
- package/rules/bdd.md +1 -1
- package/rules/cleanup-temp-files.md +10 -4
- package/rules/code-standards.md +7 -0
- package/rules/conservative-action.md +1 -5
- package/rules/context7.md +0 -4
- package/rules/destructive-commands.md +47 -0
- package/rules/doc-inventory-integrity.md +48 -0
- package/rules/doc-prose-cuts.md +58 -0
- package/rules/docstring-prose-matches-implementation.md +10 -2
- package/rules/durable-post-artifacts.md +0 -4
- package/rules/eli11-replies.md +31 -0
- package/rules/explore-thoroughly.md +4 -4
- package/rules/falsify-before-green.md +68 -0
- package/rules/file-global-constants.md +1 -1
- package/rules/filesystem-search.md +51 -0
- package/rules/gh-cli-conventions.md +27 -0
- package/rules/git-workflow.md +26 -0
- package/rules/hedging-claims.md +9 -0
- package/rules/long-horizon-autonomy.md +0 -4
- package/rules/measurement-denominators.md +48 -0
- package/rules/nas-ssh-invocation.md +23 -5
- package/rules/parallel-tools.md +2 -2
- package/rules/plain-illustrative-docstrings.md +3 -7
- package/rules/plain-language.md +2 -0
- package/rules/proof-of-work-pr-comments.md +0 -4
- package/rules/re-stage-before-commit.md +2 -0
- package/rules/research-mode.md +10 -0
- package/rules/shell-invocation.md +21 -0
- package/rules/testing.md +4 -0
- package/rules/verified-commit-gate-skip.md +3 -27
- package/rules/verify-before-asking.md +5 -0
- package/rules/windows-filesystem-safe.md +1 -1
- package/rules/workers-done-before-complete.md +4 -0
- package/scripts/CLAUDE.md +3 -3
- package/scripts/Migrate-ShellPolicy.ps1 +1 -1
- package/scripts/codex_capability_bridge.py +171 -0
- package/scripts/codex_compat_materializer.py +1087 -0
- package/scripts/codex_compat_watcher.py +502 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
- package/scripts/dev_env_scripts_constants/code_review_constants.py +37 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +79 -13
- package/scripts/grok_headless_runner.py +213 -16
- package/scripts/invoke_code_review.py +11 -4
- package/scripts/resolve_worker_spawn.py +56 -10
- package/scripts/spawn_grok_batch.py +43 -22
- package/scripts/sync_to_cursor/rules.py +0 -10
- package/scripts/test_grok_headless_runner.py +592 -10
- package/scripts/test_invoke_code_review.py +143 -0
- package/scripts/test_invoke_code_review_chain.py +1 -1
- package/scripts/test_invoke_code_review_contract.py +1 -1
- package/scripts/test_resolve_worker_spawn.py +179 -15
- package/scripts/test_spawn_grok_batch.py +225 -22
- package/scripts/tests/test_code_review_constants.py +80 -0
- package/scripts/tests/test_codex_capability_bridge.py +91 -0
- package/scripts/tests/test_codex_compat_materializer.py +632 -0
- package/scripts/tests/test_codex_compat_watcher.py +599 -0
- package/scripts/tests/test_sync_to_cursor.py +0 -1
- package/skills/autoconverge/workflow/converge.contract.test.mjs +28 -6
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +73 -0
- package/skills/autoconverge/workflow/converge.mjs +64 -15
- package/skills/bugteam/reference/copilot-gap-analysis.md +1 -1
- package/skills/condensing-instructions/SKILL.md +42 -51
- package/skills/fresh-branch/CLAUDE.md +1 -1
- package/skills/fresh-branch/SKILL.md +5 -6
- package/skills/fresh-branch/scripts/create_fresh_branch.py +42 -24
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -3
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +30 -126
- package/skills/grok-spawn/SKILL.md +5 -3
- package/skills/grok-spawn/reference/flag-profiles.md +3 -1
- package/skills/orchestrator/SKILL.md +23 -9
- package/skills/orchestrator-refresh/SKILL.md +20 -1
- package/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
- package/skills/session-log/SKILL.md +1 -1
- package/rules/claude-md-orphan-file.md +0 -28
- package/rules/cleanup-command-forms.md +0 -23
- package/rules/code-reviews.md +0 -11
- package/rules/env-var-table-code-drift.md +0 -10
- package/rules/gh-body-file.md +0 -5
- package/rules/gh-paginate.md +0 -3
- package/rules/hook-prose-matches-detector.md +0 -15
- package/rules/no-historical-clutter.md +0 -26
- package/rules/no-inline-destructive-literals.md +0 -9
- package/rules/no-justification-noise.md +0 -61
- package/rules/package-inventory-stale-entry.md +0 -25
- package/rules/right-sized-engineering.md +0 -28
- package/rules/self-contained-docs.md +0 -17
- package/rules/shell-invocation-policy.md +0 -5
- package/rules/state-what-is.md +0 -25
- package/rules/tdd.md +0 -7
|
@@ -258,6 +258,7 @@ const verifyObjectionModule = new Function(
|
|
|
258
258
|
`${functionSource('parseLastVerdictFence')}\n` +
|
|
259
259
|
`${constantLine('VERIFY_OBJECTION_FALLBACK')}\n` +
|
|
260
260
|
`${functionSource('renderVerifyObjectionLine')}\n` +
|
|
261
|
+
`${functionSource('extractPreFenceProse')}\n` +
|
|
261
262
|
`${functionSource('extractVerifyObjection')}\n` +
|
|
262
263
|
'return { extractVerifyObjection, VERIFY_OBJECTION_FALLBACK };',
|
|
263
264
|
)();
|
|
@@ -338,6 +339,78 @@ test('extractVerifyObjection falls back when no finding yields usable text', ()
|
|
|
338
339
|
assert.equal(extractVerifyObjection(transcript), VERIFY_OBJECTION_FALLBACK);
|
|
339
340
|
});
|
|
340
341
|
|
|
342
|
+
test('the prose reader anchors on the last CLOSED fence, not a stray unterminated marker', () => {
|
|
343
|
+
const transcript =
|
|
344
|
+
'check X never showed red\n\n' +
|
|
345
|
+
'```verdict\n{"all_pass": false, "findings": []}\n```\n\n' +
|
|
346
|
+
'and here I started to restate it\n\n' +
|
|
347
|
+
'```verdict';
|
|
348
|
+
const objection = extractVerifyObjection(transcript);
|
|
349
|
+
assert.equal(objection, 'check X never showed red');
|
|
350
|
+
assert.doesNotMatch(objection, /all_pass/, 'expected the verdict body never to be read back as prose');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('the prose reader skips a scaffolding-only paragraph above the fence', () => {
|
|
354
|
+
const transcript =
|
|
355
|
+
'check X never showed red\n\n' + '## Verdict\n\n' + '```verdict\n{"all_pass": false, "findings": []}\n```';
|
|
356
|
+
assert.equal(extractVerifyObjection(transcript), 'check X never showed red');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const verifyRecoveryPromptModule = new Function(
|
|
360
|
+
`${constantLine('VERIFY_OBJECTION_FALLBACK')}\n` +
|
|
361
|
+
'const prCoordinates = "owner/repo#1";\n' +
|
|
362
|
+
'const PRE_COMMIT_GATE_STEP = "";\n' +
|
|
363
|
+
'const EDIT_SCHEMA = {};\n' +
|
|
364
|
+
'const TIERS = { sonnetMedium: {} };\n' +
|
|
365
|
+
'const convergeAgent = (spawnPrompt) => spawnPrompt;\n' +
|
|
366
|
+
`${functionSource('runCodeEditorTask')}\n` +
|
|
367
|
+
'return { runCodeEditorTask };',
|
|
368
|
+
)();
|
|
369
|
+
|
|
370
|
+
const { runCodeEditorTask: buildCodeEditorPrompt } = verifyRecoveryPromptModule;
|
|
371
|
+
|
|
372
|
+
function buildVerifyRecoveryPrompt(verifyTranscript) {
|
|
373
|
+
return buildCodeEditorPrompt('verify-recover', {
|
|
374
|
+
objection: extractVerifyObjection(verifyTranscript),
|
|
375
|
+
head: 'deadbeefcafe',
|
|
376
|
+
sourceLabel: 'round-1 lens findings',
|
|
377
|
+
attempt: 1,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const INCOMPLETE_CHECK_SENTENCE =
|
|
382
|
+
'This verdict is incomplete: the deliberate break for check_docstring_runon_sentence never showed red, so that gate is unproven rather than failed.';
|
|
383
|
+
|
|
384
|
+
test('an incomplete verdict carries its named check into the fixer prompt instead of the fallback', () => {
|
|
385
|
+
const transcript =
|
|
386
|
+
'I ran the named gates and read the diff against the task text.\n\n' +
|
|
387
|
+
`${INCOMPLETE_CHECK_SENTENCE}\n\n` +
|
|
388
|
+
'```verdict\n{"all_pass": false, "findings": [], "manifest_sha256": "0f1e2d"}\n```';
|
|
389
|
+
const fixerPrompt = buildVerifyRecoveryPrompt(transcript);
|
|
390
|
+
assert.match(fixerPrompt, /VERIFY-RECOVERY fixer/, 'expected the verify-recovery fixer prompt');
|
|
391
|
+
assert.ok(
|
|
392
|
+
fixerPrompt.includes(INCOMPLETE_CHECK_SENTENCE),
|
|
393
|
+
`expected the fixer prompt to carry the incomplete-check sentence, got:\n${fixerPrompt.slice(0, 600)}`,
|
|
394
|
+
);
|
|
395
|
+
assert.doesNotMatch(
|
|
396
|
+
fixerPrompt,
|
|
397
|
+
/without a parseable verdict/,
|
|
398
|
+
'expected the incomplete verdict never to reach the fixer as VERIFY_OBJECTION_FALLBACK',
|
|
399
|
+
);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('a genuine code defect still reaches the fixer prompt from findings, unchanged by pre-fence prose', () => {
|
|
403
|
+
const transcript =
|
|
404
|
+
`${INCOMPLETE_CHECK_SENTENCE}\n\n` +
|
|
405
|
+
'```verdict\n{"all_pass": false, "findings": [{"check": "Finding 1", "detail": "boundary still over-blocks"}]}\n```';
|
|
406
|
+
const fixerPrompt = buildVerifyRecoveryPrompt(transcript);
|
|
407
|
+
assert.match(fixerPrompt, /1\. Finding 1 — boundary still over-blocks/);
|
|
408
|
+
assert.ok(
|
|
409
|
+
!fixerPrompt.includes(INCOMPLETE_CHECK_SENTENCE),
|
|
410
|
+
'expected findings to keep owning a verdict that names a code defect',
|
|
411
|
+
);
|
|
412
|
+
});
|
|
413
|
+
|
|
341
414
|
test('the verify-recover task in runCodeEditorTask is a clean-coder edit step bound to the verifier objection and leaves changes uncommitted', () => {
|
|
342
415
|
const recoverBody = functionSource('runCodeEditorTask');
|
|
343
416
|
assert.match(recoverBody, /agentType:\s*'clean-coder'/, 'expected the fixer to use clean-coder');
|
|
@@ -68,13 +68,15 @@ const HEADLESS_EDIT_PREAMBLE =
|
|
|
68
68
|
'- When your run was given a result schema, your final action is always the StructuredOutput call. If the poll budget is spent before the awaited signal arrives, call StructuredOutput with the whole time-out result the step documents — for the Copilot gate, the full down result {sha, clean:false, down:true, findings:[]}, never a bare down flag — rather than ending the turn without a result.\n\n'
|
|
69
69
|
|
|
70
70
|
const HEADLESS_READONLY_DESTRUCTIVE_POINTER =
|
|
71
|
-
'- Never run a destructive command (rm -rf, git reset --hard, dd, mkfs, chmod -R, a fork bomb) and never place its literal text in a Bash command: this step
|
|
71
|
+
'- Never run a destructive command (rm -rf, git reset --hard, dd, mkfs, chmod -R, a fork bomb) and never place its literal text in a Bash command: this step makes no edit to the tree it reads, so it needs no destructive command. If a step seems to require one, report it as a blocker rather than running it.\n'
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* The read-only preamble a review, verify, or utility agent receives: the full
|
|
75
|
-
* edit preamble with the rm-shape-rules bullet dropped, since an agent that
|
|
76
|
-
*
|
|
77
|
-
*
|
|
75
|
+
* edit preamble with the rm-shape-rules bullet dropped, since an agent that makes
|
|
76
|
+
* no edit in the tree it reads never runs rm against that tree, and the one file
|
|
77
|
+
* it may touch off that tree — a deliberate break at a break site outside it —
|
|
78
|
+
* is an edit rather than a delete, so the shape rules add no value to its
|
|
79
|
+
* prompt. The one-line destructive pointer keeps the escape-hatch guidance in view. The
|
|
78
80
|
* derivation reads the single rm-shape bullet out of the edit preamble and swaps
|
|
79
81
|
* the pointer in, so the two preambles share every other clause from one source.
|
|
80
82
|
*/
|
|
@@ -381,7 +383,7 @@ function runCodeEditorTask(task, context) {
|
|
|
381
383
|
`You are the COMMIT step opening the environment-hardening PR (${context.sourceLabel}) for the change staged in ${context.hardeningRepoPath} on branch ${context.hardeningBranch}. The edit step left the hooks/rules edits in the working tree and the verify step passed, so a verifier verdict already binds to this exact working tree. Do NOT touch the PR's own branch.\n\n` +
|
|
382
384
|
`Rules:\n` +
|
|
383
385
|
`- Make NO further file edits of any kind. Any edit changes the surface and invalidates the verdict that unlocks the commit gate, so the push would be blocked. Only commit and push what is already there.\n` +
|
|
384
|
-
`- In ${context.hardeningRepoPath}: make ONE commit of the staged hooks/rules change on branch ${context.hardeningBranch}, push it, then open a DRAFT PR. The PR body references the follow-up issue ${context.issueUrl || '(none)'} and states the PR hardens the environment so the deferred violation classes are blocked at Write/Edit time. Honor the gh-
|
|
386
|
+
`- In ${context.hardeningRepoPath}: make ONE commit of the staged hooks/rules change on branch ${context.hardeningBranch}, push it, then open a DRAFT PR. The PR body references the follow-up issue ${context.issueUrl || '(none)'} and states the PR hardens the environment so the deferred violation classes are blocked at Write/Edit time. Honor the gh-cli-conventions rule: write a BOM-free temp file and pass --body-file.\n` +
|
|
385
387
|
`- Title the PR as a Conventional Commit — a type prefix (feat, fix, chore, docs, refactor, perf, ci, style, test, build, revert), an optional scope in parentheses, then a colon and a short summary, e.g. "feat(hooks): block the deferred violation class". The target repo's CI validates the PR title as a semantic commit and rejects a non-conforming title.\n\n` +
|
|
386
388
|
`Return the full https URL of the DRAFT hardening PR in hardeningPrUrl (empty string when no PR was opened) and a one-line summary.`,
|
|
387
389
|
{ label, phase: 'Converge', schema: HARDENING_COMMIT_SCHEMA, agentType: 'clean-coder', ...TIERS.sonnetMedium },
|
|
@@ -430,7 +432,7 @@ function runVerifierTask(task, context) {
|
|
|
430
432
|
if (task === 'fix-verify') {
|
|
431
433
|
const findingsBlock = renderFindingsBlock(context.findings)
|
|
432
434
|
return convergeReadOnlyAgent(
|
|
433
|
-
`You are the VERIFY step for ${context.findings.length} finding(s) (${context.sourceLabel}) on ${prCoordinates}, HEAD ${context.head}. The edit step left fixes in the working tree, uncommitted.
|
|
435
|
+
`You are the VERIFY step for ${context.findings.length} finding(s) (${context.sourceLabel}) on ${prCoordinates}, HEAD ${context.head}. The edit step left fixes in the working tree, uncommitted. Make NO edit to the tree under verification — verification only; any edit inside that tree invalidates the verdict you are about to emit.\n\n` +
|
|
434
436
|
`Findings the working-tree fixes must address:\n${findingsBlock}\n\n` +
|
|
435
437
|
`Steps:\n` +
|
|
436
438
|
`1. Resolve the worktree repo root for running tests: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
@@ -444,7 +446,7 @@ function runVerifierTask(task, context) {
|
|
|
444
446
|
? context.failures.map((each, position) => `${position + 1}. ${each}`).join('\n')
|
|
445
447
|
: 'none reported'
|
|
446
448
|
return convergeReadOnlyAgent(
|
|
447
|
-
`You are the VERIFY step for the convergence repair on ${prCoordinates}, HEAD ${context.head}. The edit step left its repair in the working tree (a bot-thread fix uncommitted, and/or a rebase onto origin/main), unpushed.
|
|
449
|
+
`You are the VERIFY step for the convergence repair on ${prCoordinates}, HEAD ${context.head}. The edit step left its repair in the working tree (a bot-thread fix uncommitted, and/or a rebase onto origin/main), unpushed. Make NO edit to the tree under verification — verification only; any edit inside that tree invalidates the verdict you are about to emit.\n\n` +
|
|
448
450
|
`Concerns the working-tree repair must resolve (the gates the convergence check flagged):\n${failureBlock}\n\n` +
|
|
449
451
|
`Steps:\n` +
|
|
450
452
|
`1. Resolve the worktree repo root for running tests: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
@@ -454,7 +456,7 @@ function runVerifierTask(task, context) {
|
|
|
454
456
|
)
|
|
455
457
|
}
|
|
456
458
|
return convergeReadOnlyAgent(
|
|
457
|
-
`You are the VERIFY step for an environment-hardening change (${context.sourceLabel}) staged in the working tree of ${context.hardeningRepoPath}. The edit step left the hooks/rules edits uncommitted there.
|
|
459
|
+
`You are the VERIFY step for an environment-hardening change (${context.sourceLabel}) staged in the working tree of ${context.hardeningRepoPath}. The edit step left the hooks/rules edits uncommitted there. Make NO edit to the tree under verification — verification only; any edit inside that tree invalidates the verdict you are about to emit.\n\n` +
|
|
458
460
|
`Concern the working-tree change must resolve: the edited hooks/rules block the code-standard violation classes from the deferred round at Write/Edit time, and a hook change carries a passing test per CODE_RULES.\n\n` +
|
|
459
461
|
`Steps:\n` +
|
|
460
462
|
`1. cd into ${context.hardeningRepoPath}, then resolve its repo root: REPO=$(git rev-parse --show-toplevel).\n` +
|
|
@@ -468,7 +470,7 @@ function runVerifierTask(task, context) {
|
|
|
468
470
|
" ```verdict\n" +
|
|
469
471
|
` {"all_pass": true, "findings": [], "manifest_sha256": "<that hash>"}\n` +
|
|
470
472
|
" ```\n" +
|
|
471
|
-
`
|
|
473
|
+
` Set all_pass to false when verification fails, and list every code defect you found in findings. When the verdict is incomplete because a check it rests on never showed red, set all_pass to false and name that check in prose directly above the fence rather than in findings. Always include the manifest_sha256. The verdict fence must be the last thing in your message.`,
|
|
472
474
|
{ label, phase: 'Converge', agentType: 'code-verifier', ...TIERS.sonnetMedium },
|
|
473
475
|
)
|
|
474
476
|
}
|
|
@@ -871,7 +873,7 @@ function buildVerdictFenceSteps(prOwner, prRepo, prNumber) {
|
|
|
871
873
|
" ```verdict\n" +
|
|
872
874
|
` {"all_pass": true, "findings": [], "manifest_sha256": "<that hash>"}\n` +
|
|
873
875
|
" ```\n" +
|
|
874
|
-
`
|
|
876
|
+
` Set all_pass to false when verification fails, and list every code defect you found in findings. When the verdict is incomplete because a check it rests on never showed red, set all_pass to false and name that check in prose directly above the fence rather than in findings. Always include the manifest_sha256. The verdict fence must be the last thing in your message.`
|
|
875
877
|
)
|
|
876
878
|
}
|
|
877
879
|
|
|
@@ -1343,14 +1345,61 @@ function renderVerifyObjectionLine(eachFinding) {
|
|
|
1343
1345
|
return stringifiedFinding === '{}' ? null : stringifiedFinding
|
|
1344
1346
|
}
|
|
1345
1347
|
|
|
1348
|
+
/**
|
|
1349
|
+
* Read the prose paragraph the verifier wrote directly above its last verdict
|
|
1350
|
+
* fence. A verdict that is incomplete — a check it rests on never showed red —
|
|
1351
|
+
* is not a code defect, so the fence contract puts that reason here rather than
|
|
1352
|
+
* in findings; this reader is how the reason survives into the re-fix step.
|
|
1353
|
+
*
|
|
1354
|
+
* Anchors on the same fence parseLastVerdictFence reads — the last CLOSED
|
|
1355
|
+
* verdict fence, not the last bare marker — so a stray unterminated marker after
|
|
1356
|
+
* the real fence can never make this read the verdict body itself as prose. A
|
|
1357
|
+
* candidate paragraph carrying only markdown scaffolding (a heading, a fence
|
|
1358
|
+
* delimiter) is skipped, since handing the fixer a heading is worse than the
|
|
1359
|
+
* generic fallback the caller keeps.
|
|
1360
|
+
*
|
|
1361
|
+
* ::
|
|
1362
|
+
*
|
|
1363
|
+
* 'ran the gates\n\ncheck X never showed red\n\n```verdict\n{}\n```'
|
|
1364
|
+
* -> 'check X never showed red'
|
|
1365
|
+
* '```verdict\n{}\n```' -> null
|
|
1366
|
+
*
|
|
1367
|
+
* @param {string|null|undefined} verifyTranscript the verifier transcript text
|
|
1368
|
+
* @returns {string|null} the last prose paragraph above the final fence, or null when there is none
|
|
1369
|
+
*/
|
|
1370
|
+
function extractPreFenceProse(verifyTranscript) {
|
|
1371
|
+
if (typeof verifyTranscript !== 'string') return null
|
|
1372
|
+
const fenceMarker = '```verdict'
|
|
1373
|
+
let lastFenceStart = verifyTranscript.lastIndexOf(fenceMarker)
|
|
1374
|
+
while (lastFenceStart !== -1 && !verifyTranscript.slice(lastFenceStart + fenceMarker.length).includes('```')) {
|
|
1375
|
+
lastFenceStart = verifyTranscript.lastIndexOf(fenceMarker, lastFenceStart - 1)
|
|
1376
|
+
}
|
|
1377
|
+
if (lastFenceStart === -1) return null
|
|
1378
|
+
const proseParagraphs = verifyTranscript
|
|
1379
|
+
.slice(0, lastFenceStart)
|
|
1380
|
+
.split(/\n\s*\n/)
|
|
1381
|
+
.map((eachParagraph) => eachParagraph.trim())
|
|
1382
|
+
.filter((eachParagraph) =>
|
|
1383
|
+
eachParagraph
|
|
1384
|
+
.split('\n')
|
|
1385
|
+
.some((eachLine) => {
|
|
1386
|
+
const trimmedLine = eachLine.trim()
|
|
1387
|
+
return trimmedLine.length > 0 && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('```') && /[A-Za-z]/.test(trimmedLine)
|
|
1388
|
+
}),
|
|
1389
|
+
)
|
|
1390
|
+
return proseParagraphs.length === 0 ? null : proseParagraphs[proseParagraphs.length - 1]
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1346
1393
|
/**
|
|
1347
1394
|
* Pull the verifier's stated objections out of a failed verify transcript so the
|
|
1348
1395
|
* re-fix step knows what the verdict rejected. Reads the last fenced verdict JSON
|
|
1349
1396
|
* (the same block verdictPassed reads) and renders each finding through
|
|
1350
|
-
* renderVerifyObjectionLine into a numbered list.
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1397
|
+
* renderVerifyObjectionLine into a numbered list. When findings yields no usable
|
|
1398
|
+
* line the verdict is an incomplete one rather than a code-defect one, so the
|
|
1399
|
+
* prose paragraph above the fence — where the fence contract puts the unshown-red
|
|
1400
|
+
* check — carries the objection instead. A missing fence, a parse failure, or an
|
|
1401
|
+
* empty findings list with no prose above the fence falls back to a generic
|
|
1402
|
+
* re-read instruction, so the re-fix step always receives actionable text.
|
|
1354
1403
|
* @param {string|null|undefined} verifyTranscript the failed verifier transcript text
|
|
1355
1404
|
* @returns {string} a human-readable block of the verifier's objections
|
|
1356
1405
|
*/
|
|
@@ -1361,7 +1410,7 @@ function extractVerifyObjection(verifyTranscript) {
|
|
|
1361
1410
|
const renderedObjections = allObjections
|
|
1362
1411
|
.map((eachFinding) => renderVerifyObjectionLine(eachFinding))
|
|
1363
1412
|
.filter((eachLine) => eachLine !== null)
|
|
1364
|
-
if (renderedObjections.length === 0) return VERIFY_OBJECTION_FALLBACK
|
|
1413
|
+
if (renderedObjections.length === 0) return extractPreFenceProse(verifyTranscript) || VERIFY_OBJECTION_FALLBACK
|
|
1365
1414
|
return renderedObjections.map((eachLine, position) => `${position + 1}. ${eachLine}`).join('\n')
|
|
1366
1415
|
}
|
|
1367
1416
|
|
|
@@ -510,4 +510,4 @@ Expected output after the patch lands: a list containing `Line 91: Collection co
|
|
|
510
510
|
- Inventory data sources: live `gh api repos/example-org/example-repo/pulls/{101,102}/comments` filtered to `Copilot`; verbatim bodies preserved in the inventory table above.
|
|
511
511
|
- Original-commit content used to confirm violations: PR #101 head `29117309cf4ec1e83883160d8c819e0843f9c3ac`; PR #102 review-time commit `e4abf52c3a6c724b4e64bfed0d979cd60a2c8bf0`; PR #102 merged head `c9c935a96cc59d39d623dc7eddda3d341007607c`.
|
|
512
512
|
- CODE_RULES.md sections invoked by the patch plan: §⚡ Magic values; §5 Extended naming rules (collections `all_orders`, `all_users`); §6.5 File length guidance; §7 Right-Sized Engineering; §10 No redundant data fetches (used as analogue for wrapper plumb-through).
|
|
513
|
-
- Constraints honored: `gh-
|
|
513
|
+
- Constraints honored: `gh-cli-conventions` (no `gh ... --body` calls in the new code paths), `shell-invocation` (no `$(...)` in the verification commands above; multi-step shell flows are written as separate Bash invocations or `&&`-chained literal strings).
|
|
@@ -1,81 +1,72 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: condensing-instructions
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
Refine an instruction document for Claude 5 generation models: cut rules the
|
|
5
|
+
model's judgment already covers, move detail behind progressive disclosure,
|
|
6
|
+
and route each remaining piece to the system prompt, CLAUDE.md, a skill, or a
|
|
7
|
+
reference. Use for system prompts, CLAUDE.md files, skills, tool
|
|
8
|
+
descriptions, agent instructions, context engineering, prompt slimming, and
|
|
9
|
+
token reduction.
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# Condensing Instructions
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Cut an instruction document to what a Claude 5 generation model needs, and move the rest to the surface that loads it on demand. Over 80% of Claude Code's system prompt came out for Claude Opus 5 and Claude Fable 5 with no measurable drop in performance, so treat a long instruction document as a place with room to cut.
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## Clear the conflicts first
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Read the system prompt, the CLAUDE.md, and the skills as one body of text and find the lines that pull against each other — "leave documentation as appropriate" sitting beside "DO NOT add comments". A conflict makes the model deliberate at length before it acts. Resolve each pair into one statement, or delete both when neither changes what the model does.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
## Trade rules for judgment
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
Delete a rule written to block a worst case, such as file deletion. Claude 5 generation models read the surrounding context and decide well without it, and a rigid rule blocks the user who has a real reason to want the behavior it forbids.
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
State the outcome and the signal the model should read:
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
- Cut: "Default to writing no comments. Never write multi-paragraph docstrings or multi-line comment blocks — one short line max. Don't create planning, decision, or analysis documents unless the user asks for them."
|
|
27
|
+
- Keep: "Write code that reads like the surrounding code: match its comment density, naming, and idiom."
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
## Design the interface in place of examples
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
Examples constrain exploration. Carry usage in the tool's own shape: expressive names, expressive parameters, and types that signal intent. A status parameter enumerated as `pending`, `in_progress`, `completed` shows correct use with no example attached.
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
## Load detail at the point of use
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
Move detail out of always-on text and into a surface the model reaches for:
|
|
34
36
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
- Within a group, put prerequisites before actions, general rules before narrow exceptions, and production requirements before their checks.
|
|
39
|
-
- Put each exception beside the rule it modifies. State precedence when rules overlap.
|
|
40
|
-
- Give each rule one authoritative location. State a shared actor, condition, default, or scope once at the narrowest level that covers every affected rule.
|
|
41
|
-
- Let a heading carry scope only when every instruction beneath it clearly inherits that scope. Use the fewest headings that preserve navigation.
|
|
37
|
+
- A skill the model calls when the task calls for it. Code review and verification detail belongs here.
|
|
38
|
+
- A linked file the model opens on demand.
|
|
39
|
+
- A tool with deferred loading, where the model searches for the definition before it uses the tool.
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
Keep the always-on context lean and let the model pull the rest.
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
- Preserve force and quantifiers. Keep `must`, `never`, `only`, `should`, `may`, `all`, `any`, and exact counts distinct.
|
|
47
|
-
- Combine statements only when their actor, force, scope, trigger, timing, persistence, and exceptions align.
|
|
48
|
-
- Replace true repetition with one rule and a compact list of affected cases. Keep separate statements when repetition protects distinct stages, scopes, or failure modes.
|
|
49
|
-
- Use one sentence per decision. Join clauses only when they form one testable rule.
|
|
50
|
-
- Use paragraphs for cohesive rules and lists for parallel obligations, mappings, or branches. Avoid decorative headings and structural ceremony.
|
|
51
|
-
- Remove non-operative background, history, rationale, transition text, conversational framing, restatement, setup narration, and examples. Remove inventories or folder maps that merely describe the document.
|
|
52
|
-
- Use one term for each concept. Define unfamiliar terms at first use. Do not compress complete sentences into fragments, stacked jargon, or vague shorthand.
|
|
53
|
-
- Preserve operational literals character for character, including spelling, case, punctuation, quoting, and placeholders. Preserve whether a list is exhaustive or illustrative; never replace an exact enumeration with `etc.` or a broader category.
|
|
43
|
+
## Say each thing once
|
|
54
44
|
|
|
55
|
-
|
|
45
|
+
Put tool usage guidance in the tool description alone. Delete the copy that repeats it in the system prompt.
|
|
56
46
|
|
|
57
|
-
|
|
47
|
+
## Let memory carry session facts
|
|
58
48
|
|
|
59
|
-
|
|
49
|
+
Claude's automatic memory captures relevant context and carries it across sessions. Delete hand-written memory notes from CLAUDE.md when memory already holds them.
|
|
60
50
|
|
|
61
|
-
|
|
51
|
+
## Point at rich references
|
|
62
52
|
|
|
63
|
-
|
|
53
|
+
Include files as references with @mentions. Prefer a reference the model can read with no ambiguity, in this order:
|
|
64
54
|
|
|
65
|
-
|
|
55
|
+
1. Code from this or another codebase — the highest fidelity specification available.
|
|
56
|
+
2. A test suite that pins the behavior.
|
|
57
|
+
3. An HTML artifact or a mockup.
|
|
58
|
+
4. A rubric, which lets a verification agent score work against a quality standard.
|
|
59
|
+
5. Prose description or a screenshot.
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
2. **Support:** Map every material clause back to a stated or confirmed requirement; remove invented behavior.
|
|
69
|
-
3. **Fidelity:** Compare force, negation, quantifiers, actor, object or type, scope, conditions, exceptions, dependencies, order, timing, persistence, failure behavior, outputs, and protected literals. Nothing may be weakened, broadened, narrowed, contradicted, or altered character for character where exactness matters.
|
|
70
|
-
4. **Boundary behavior:** Test the normal case, each branch and exception, prohibited cases, missing dependencies, failures, and acceptance checks when present. Each case must yield the intended action.
|
|
71
|
-
5. **Clarity:** Resolve ambiguous references, hidden precedence, scattered exceptions, inconsistent terms, and unclear qualifier scope.
|
|
72
|
-
6. **Density:** Delete any sentence that changes no behavior, safety boundary, interpretation, or validation result. Merge remaining text only when the boundary stays equally clear.
|
|
73
|
-
7. **Independent use:** Reconstruct the ledger from only the finished document. It must stand alone without background or unstated context.
|
|
61
|
+
## Route what remains
|
|
74
62
|
|
|
75
|
-
|
|
63
|
+
| Surface | What belongs there |
|
|
64
|
+
|---|---|
|
|
65
|
+
| System prompt | The product Claude works within and the role it plays. For a custom agent, spend real effort here. |
|
|
66
|
+
| CLAUDE.md | A short line on what the repository is, then the gotchas found inside the codebase. Drop anything the model can read off the file structure. Link to skills for the detail. |
|
|
67
|
+
| Skills | Opinions, knowledge, and practices particular to your team or product, written as a guide the model consults. Split a long skill into several files. Constrain only where it matters. |
|
|
68
|
+
| References | Specs, mockups, and codebases pulled in by @mention. |
|
|
76
69
|
|
|
77
|
-
## Deliver
|
|
70
|
+
## Deliver
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
For text supplied in the conversation, output only the finished instruction document. For file tasks, write only the requested paths: edit an existing file in place, and create a new file only when requested. Report changed paths with a concise behavior summary. Do not include the ledger or process narration unless requested.
|
|
72
|
+
Rewrite the document in place and report what moved to which surface. Offer `claude doctor` as a follow-up pass — it rightsizes skills and CLAUDE.md files against these same rules.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# fresh-branch
|
|
2
2
|
|
|
3
|
-
Creates a new branch from fresh-fetched `origin/main` inside an isolated worktree under
|
|
3
|
+
Creates a new branch from fresh-fetched `origin/main` inside an isolated worktree under `<repo-root>/.claude/worktrees/<agent>/<branch-name>`. Does not push, open a PR, or run `checkout -b` in the caller tree.
|
|
4
4
|
|
|
5
5
|
## Key files
|
|
6
6
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fresh-branch
|
|
3
3
|
description: >-
|
|
4
|
-
Fresh git branch from origin/main in an isolated
|
|
4
|
+
Fresh git branch from origin/main in an isolated worktree under the repo's .claude/worktrees/ (never checkout -b in the caller tree).
|
|
5
5
|
Triggers: fresh branch, new branch from main, /fresh-branch, start fresh, clean branch off main,
|
|
6
6
|
worktree branch, branch in temp.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# fresh-branch
|
|
10
10
|
|
|
11
|
-
Creates a new branch from a fresh-fetched `origin/main` inside an isolated git worktree under the agent
|
|
11
|
+
Creates a new branch from a fresh-fetched `origin/main` inside an isolated git worktree under the repository's `.claude/worktrees/<agent>/` root. Shared primitive: other skills invoke `/fresh-branch` when they need a clean branch without touching the caller's dirty tree.
|
|
12
12
|
|
|
13
13
|
**Announce at start:** "Creating a fresh branch from origin/main."
|
|
14
14
|
|
|
@@ -62,15 +62,14 @@ Optional flags:
|
|
|
62
62
|
| Flag | Role |
|
|
63
63
|
|------|------|
|
|
64
64
|
| `--repo <path>` | Source repo (default: current directory) |
|
|
65
|
-
| `--agent <slug>` |
|
|
65
|
+
| `--agent <slug>` | Worktree-root segment: `claude`, `grok`, `cursor`, `codex`, … |
|
|
66
66
|
| `--base <ref>` | Base ref (default: `origin/main`) |
|
|
67
67
|
|
|
68
68
|
Agent resolution inside the script: `--agent` → `FRESH_BRANCH_AGENT` env → host markers → `claude`.
|
|
69
69
|
|
|
70
70
|
Worktree path:
|
|
71
71
|
|
|
72
|
-
-
|
|
73
|
-
- Else: `${tmpdir}/<agent>/<branch-name>`
|
|
72
|
+
- `<repo-root>/.claude/worktrees/<agent>/<branch-name>`, on every platform
|
|
74
73
|
- If the path exists, the script suffixes `-2`, `-3`, …
|
|
75
74
|
|
|
76
75
|
On exit 0, stdout is one JSON object:
|
|
@@ -101,7 +100,7 @@ Further edits for the new branch belong in `worktree_path`, not in the caller's
|
|
|
101
100
|
|
|
102
101
|
## Gotchas
|
|
103
102
|
|
|
104
|
-
- **Dirty caller cwd blocks `checkout -b` and pollutes the tree.** Phase 3 always uses `git worktree add -b` into
|
|
103
|
+
- **Dirty caller cwd blocks `checkout -b` and pollutes the tree.** Phase 3 always uses `git worktree add -b` into `<repo-root>/.claude/worktrees/<agent>/…`. If you reconstruct Phase 3 by hand with `checkout -b` in the session cwd, local modifications block the checkout and leave the user on a half-switched branch.
|
|
105
104
|
- **Caller HEAD must stay put.** After success, the original repo's checked-out branch and dirty files are unchanged; only the new worktree has the new branch.
|
|
106
105
|
- **Branch name collision.** If the branch already exists, the script exits non-zero with `{"error":...}`. Pick a new name; do not delete remote branches unless the user asks.
|
|
107
106
|
- **Path already occupied.** A leftover folder at the preferred worktree path gets a numeric suffix (`-2`, …); report the path from JSON, not the path you assumed.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Create a fresh branch in an isolated git worktree under the
|
|
2
|
+
"""Create a fresh branch in an isolated git worktree under the repository.
|
|
3
3
|
|
|
4
4
|
::
|
|
5
5
|
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"base_commit": "abc...", "agent": "claude", "repo_root": "..."}
|
|
9
9
|
|
|
10
10
|
Never runs ``git checkout -b`` in the caller's working tree. Fetches the base
|
|
11
|
-
ref, then ``git worktree add -b --no-track`` into
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
ref, then ``git worktree add -b --no-track`` into
|
|
12
|
+
``<repo-root>/.claude/worktrees/<agent>/<branch>``. Exit 0 prints success JSON;
|
|
13
|
+
any failure prints ``{"error": ...}`` and exits non-zero.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
@@ -20,7 +20,6 @@ import json
|
|
|
20
20
|
import os
|
|
21
21
|
import re
|
|
22
22
|
import sys
|
|
23
|
-
import tempfile
|
|
24
23
|
from pathlib import Path
|
|
25
24
|
|
|
26
25
|
from fresh_branch_git_commands import (
|
|
@@ -33,7 +32,7 @@ from fresh_branch_git_commands import (
|
|
|
33
32
|
from fresh_branch_scripts_constants.fresh_branch_cli_constants import (
|
|
34
33
|
AGENT_SLUG_PATTERN,
|
|
35
34
|
ALL_AGENT_DETECTION_MARKERS,
|
|
36
|
-
|
|
35
|
+
ALL_REPOSITORY_WORKTREE_ROOT_PARTS,
|
|
37
36
|
DEFAULT_AGENT_SLUG,
|
|
38
37
|
DEFAULT_BASE_REF,
|
|
39
38
|
ERROR_AGENT_SLUG_INVALID,
|
|
@@ -57,8 +56,6 @@ from fresh_branch_scripts_constants.fresh_branch_cli_constants import (
|
|
|
57
56
|
PAYLOAD_KEY_REPO_ROOT,
|
|
58
57
|
PAYLOAD_KEY_WORKTREE_PATH,
|
|
59
58
|
UNIQUE_PATH_SUFFIX_START,
|
|
60
|
-
USERPROFILE_ENV_VAR,
|
|
61
|
-
WINDOWS_PLATFORM_PREFIX,
|
|
62
59
|
)
|
|
63
60
|
|
|
64
61
|
|
|
@@ -94,28 +91,26 @@ def _detect_agent_slug_from_environment() -> str:
|
|
|
94
91
|
return DEFAULT_AGENT_SLUG
|
|
95
92
|
|
|
96
93
|
|
|
97
|
-
def resolve_agent_worktree_root(agent_slug: str) -> Path:
|
|
98
|
-
"""Return
|
|
94
|
+
def resolve_agent_worktree_root(repo_root: Path, agent_slug: str) -> Path:
|
|
95
|
+
"""Return the repository's per-agent worktree root.
|
|
99
96
|
|
|
100
97
|
::
|
|
101
98
|
|
|
102
|
-
#
|
|
103
|
-
resolve_agent_worktree_root("grok")
|
|
99
|
+
# repo_root=/srv/app, agent_slug=grok
|
|
100
|
+
resolve_agent_worktree_root(Path("/srv/app"), "grok")
|
|
101
|
+
# -> /srv/app/.claude/worktrees/grok
|
|
102
|
+
|
|
103
|
+
Every worktree sits under the repository it branches from, so a worktree
|
|
104
|
+
travels with its repository and each agent keeps its own subdirectory.
|
|
104
105
|
|
|
105
106
|
Args:
|
|
107
|
+
repo_root: Absolute path of the repository the branch comes from.
|
|
106
108
|
agent_slug: Short host label (one path segment).
|
|
107
109
|
|
|
108
110
|
Returns:
|
|
109
111
|
Directory that should hold per-branch worktree folders.
|
|
110
112
|
"""
|
|
111
|
-
|
|
112
|
-
user_profile = os.environ.get(USERPROFILE_ENV_VAR)
|
|
113
|
-
if user_profile:
|
|
114
|
-
return Path(user_profile).joinpath(
|
|
115
|
-
*ALL_WINDOWS_USER_SCRATCH_PARTS,
|
|
116
|
-
agent_slug,
|
|
117
|
-
)
|
|
118
|
-
return Path(tempfile.gettempdir()) / agent_slug
|
|
113
|
+
return repo_root.joinpath(*ALL_REPOSITORY_WORKTREE_ROOT_PARTS, agent_slug)
|
|
119
114
|
|
|
120
115
|
|
|
121
116
|
def resolve_unique_worktree_path(preferred_path: Path) -> Path:
|
|
@@ -181,13 +176,33 @@ def create_fresh_branch(
|
|
|
181
176
|
::
|
|
182
177
|
|
|
183
178
|
create_fresh_branch("fix/x", Path("."), "grok", "origin/main")
|
|
179
|
+
|
|
180
|
+
The caller's working tree is never checked out. The worktree lands under
|
|
181
|
+
the repository's ``.claude/worktrees/<agent>/<branch>``, suffixed ``-2``,
|
|
182
|
+
``-3``, … when that path is already taken.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
branch_name: Branch to create; must be a safe relative path.
|
|
186
|
+
repo_path: Any path inside the repository the branch comes from.
|
|
187
|
+
agent_slug: Short host label naming the worktree-root subdirectory.
|
|
188
|
+
base_ref: Ref the branch starts from, such as ``origin/main``.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
The success payload: branch, worktree_path, base_ref, base_commit,
|
|
192
|
+
agent, and repo_root.
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
ValueError: When the branch name or the agent slug is unsafe.
|
|
196
|
+
RuntimeError: When git refuses the fetch or the worktree add.
|
|
184
197
|
"""
|
|
185
198
|
cleaned_branch = _require_safe_branch_name(branch_name)
|
|
186
199
|
normalized_agent_slug = _normalize_agent_slug(agent_slug)
|
|
187
200
|
resolved_base_ref, repo_root, base_commit = _resolve_branch_base(
|
|
188
201
|
repo_path, base_ref,
|
|
189
202
|
)
|
|
190
|
-
worktree_path = _allocate_worktree_path(
|
|
203
|
+
worktree_path = _allocate_worktree_path(
|
|
204
|
+
cleaned_branch, normalized_agent_slug, repo_root,
|
|
205
|
+
)
|
|
191
206
|
create_worktree_branch(
|
|
192
207
|
repo_root,
|
|
193
208
|
branch_name=cleaned_branch,
|
|
@@ -241,8 +256,11 @@ def _require_safe_branch_name(branch_name: str) -> str:
|
|
|
241
256
|
return cleaned_branch
|
|
242
257
|
|
|
243
258
|
|
|
244
|
-
def _allocate_worktree_path(
|
|
245
|
-
|
|
259
|
+
def _allocate_worktree_path(
|
|
260
|
+
branch_name: str, agent_slug: str, repo_root: Path,
|
|
261
|
+
) -> Path:
|
|
262
|
+
agent_worktree_root = resolve_agent_worktree_root(repo_root, agent_slug)
|
|
263
|
+
agent_worktree_root.mkdir(parents=True, exist_ok=True)
|
|
246
264
|
preferred_path = agent_worktree_root / branch_name
|
|
247
265
|
_assert_path_is_under_agent_root(
|
|
248
266
|
candidate_path=preferred_path,
|
|
@@ -324,7 +342,7 @@ def _parse_arguments() -> argparse.Namespace:
|
|
|
324
342
|
parser.add_argument(
|
|
325
343
|
"--agent",
|
|
326
344
|
default=None,
|
|
327
|
-
help="Host label for
|
|
345
|
+
help="Host label for .claude/worktrees/<agent>/ (default: detect from environment).",
|
|
328
346
|
)
|
|
329
347
|
parser.add_argument(
|
|
330
348
|
"--base",
|
package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py
CHANGED
|
@@ -23,9 +23,7 @@ ALL_AGENT_DETECTION_MARKERS: tuple[tuple[str, str], ...] = (
|
|
|
23
23
|
PATH_SEGMENT_CURRENT = "."
|
|
24
24
|
PATH_SEGMENT_PARENT = ".."
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
ALL_WINDOWS_USER_SCRATCH_PARTS = ("AppData", "Local", "Temp")
|
|
28
|
-
USERPROFILE_ENV_VAR = "USERPROFILE"
|
|
26
|
+
ALL_REPOSITORY_WORKTREE_ROOT_PARTS = (".claude", "worktrees")
|
|
29
27
|
|
|
30
28
|
MAXIMUM_UNIQUE_PATH_ATTEMPTS = 100
|
|
31
29
|
UNIQUE_PATH_SUFFIX_START = 2
|