@marcomigozzi/trailhead 0.15.0 → 0.16.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/README.it.md +52 -14
- package/README.md +53 -15
- package/bin/auto-intro-docs.test.js +72 -0
- package/bin/auto-merge-docs.test.js +33 -0
- package/bin/auto-merge-ticket-docs.test.js +32 -0
- package/bin/auto-review-rounds-docs.test.js +45 -0
- package/bin/auto-run-docs.test.js +60 -0
- package/bin/auto-scope-docs.test.js +33 -0
- package/bin/auto-slots-docs.test.js +45 -0
- package/bin/auto-uat-docs.test.js +47 -0
- package/bin/config-dir-templates-docs.test.js +52 -0
- package/bin/decision-builds-docs.test.js +75 -0
- package/bin/fog-vs-ticket-docs.test.js +56 -0
- package/bin/grilling-holistic-review-docs.test.js +90 -0
- package/bin/lib/codex-projection.js +13 -6
- package/bin/lib/codex-projection.test.js +25 -2
- package/bin/lib/host-descriptor.js +14 -1
- package/bin/lib/host-descriptor.test.js +18 -0
- package/bin/lib/runtime-command-literals.test.js +184 -0
- package/bin/needs-you-docs.test.js +41 -0
- package/bin/pick-stress-test-docs.test.js +105 -0
- package/bin/report-status-docs.test.js +1 -1
- package/bin/trailhead.js +3 -3
- package/bin/trailhead.test.js +17 -18
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +2 -2
- package/plugins/trailhead/agents/trailhead-verify.md +2 -2
- package/plugins/trailhead/commands/auto.md +2 -2
- package/plugins/trailhead/hooks/lib/auto-merge.test.js +467 -0
- package/plugins/trailhead/hooks/lib/auto-queue.test.js +245 -0
- package/plugins/trailhead/hooks/lib/auto-run.test.js +318 -0
- package/plugins/trailhead/hooks/lib/auto-slot.test.js +278 -0
- package/plugins/trailhead/hooks/lib/checkout-in.test.js +173 -0
- package/plugins/trailhead/hooks/lib/dashboard.test.js +66 -3
- package/plugins/trailhead/hooks/lib/merge-ticket.test.js +394 -0
- package/plugins/trailhead/hooks/lib/model-defaults-review.js +50 -21
- package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +50 -0
- package/plugins/trailhead/hooks/lib/needs-you.test.js +177 -0
- package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +319 -0
- package/plugins/trailhead/hooks/lib/report-status.test.js +68 -1
- package/plugins/trailhead/hooks/lib/shell-scan.js +25 -0
- package/plugins/trailhead/hooks/lib/statusline-context.test.js +161 -0
- package/plugins/trailhead/hooks/lib/uat-followup.test.js +169 -0
- package/plugins/trailhead/hooks/lib/unblock.test.js +207 -0
- package/plugins/trailhead/hooks/lib/unmodelled.js +6 -1
- package/plugins/trailhead/hooks/mod/README.md +185 -39
- package/plugins/trailhead/hooks/mod/auto-run-view.ts +93 -0
- package/plugins/trailhead/hooks/mod/auto-run.ts +165 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +150 -25
- package/plugins/trailhead/hooks/mod/map-pane.tsx +557 -99
- package/plugins/trailhead/hooks/mod/register.tsx +298 -39
- package/plugins/trailhead/hooks/mod/repo-pin.ts +167 -0
- package/plugins/trailhead/hooks/mod/session-registry.ts +65 -0
- package/plugins/trailhead/hooks/mod/turn-state.ts +14 -0
- package/plugins/trailhead/hooks/mod/whiteboard-follow.ts +98 -0
- package/plugins/trailhead/hooks/mod/work-state-locate.ts +191 -0
- package/plugins/trailhead/hooks/mod/work-state-prompt.ts +13 -15
- package/plugins/trailhead/hooks/mod/work-state-view.ts +21 -5
- package/plugins/trailhead/hooks/mod/worktree-porcelain.ts +17 -0
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +22 -16
- package/plugins/trailhead/hooks/trailhead-labels-guard.js +231 -0
- package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +212 -0
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +336 -23
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +321 -0
- package/plugins/trailhead/skills/_shared/choices.md +7 -2
- package/plugins/trailhead/skills/_shared/configuration-reference.md +5 -5
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +26 -4
- package/plugins/trailhead/skills/_shared/substrate.md +4 -1
- package/plugins/trailhead/skills/_shared/teamwork.md +5 -5
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +2 -2
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +3 -1
- package/plugins/trailhead/skills/_shared/techniques/verify.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques.md +2 -2
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +2 -2
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +4 -3
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +1 -0
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +23 -12
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +70 -22
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +7 -6
- package/plugins/trailhead/templates/statusline-trailhead.sh +80 -15
- package/plugins/trailhead/templates/trailhead-auto-merge.js +318 -0
- package/plugins/trailhead/templates/trailhead-auto-queue.js +186 -0
- package/plugins/trailhead/templates/trailhead-auto-run.js +294 -0
- package/plugins/trailhead/templates/trailhead-auto-slot.js +239 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +88 -0
- package/plugins/trailhead/templates/trailhead-dashboard.js +32 -12
- package/plugins/trailhead/templates/trailhead-merge-ticket.js +366 -0
- package/plugins/trailhead/templates/trailhead-needs-you.js +193 -0
- package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +289 -0
- package/plugins/trailhead/templates/trailhead-report-status.js +67 -3
- package/plugins/trailhead/templates/trailhead-uat-followup.js +128 -0
- package/plugins/trailhead/templates/trailhead-unblock.js +111 -0
- package/plugins/trailhead/tests/auto-run-ui.test.ts +444 -0
- package/plugins/trailhead/tests/auto-run-view.test.ts +111 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +1839 -72
- package/plugins/trailhead/tests/map-pane-view.test.ts +308 -14
- package/plugins/trailhead/tests/map-pane-write.test.ts +51 -0
- package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +77 -0
- package/plugins/trailhead/tests/repo-pin.test.ts +123 -0
- package/plugins/trailhead/tests/session-registry.test.ts +50 -0
- package/plugins/trailhead/tests/whiteboard-follow.test.ts +90 -0
- package/plugins/trailhead/tests/work-state-locate.test.ts +33 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +276 -2
- package/plugins/trailhead/tests/work-state-prompt.test.ts +29 -35
- package/plugins/trailhead/tests/work-state-ui.test.ts +73 -1
- package/plugins/trailhead/tests/work-state-view.test.ts +19 -0
- package/plugins/trailhead/tests/work-state-worktree.test.ts +310 -0
- package/plugins/trailhead/types/index.d.ts +26 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #287: an autonomous run opens, or updates, one merge ticket per map (or
|
|
3
|
+
// the whiteboard) for the slot branches it leaves unmerged, right when each arises.
|
|
4
|
+
// Plain asserts, no framework. Run: node bin/auto-merge-ticket-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
|
|
10
|
+
const auto = fs.readFileSync(path.join(plugin, 'skills/trailhead-work/references/auto.md'), 'utf8');
|
|
11
|
+
const commands = fs.readFileSync(path.join(plugin, 'skills/_shared/substrate-commands.md'), 'utf8');
|
|
12
|
+
let passed = 0;
|
|
13
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
14
|
+
|
|
15
|
+
ok('auto.md: names the add call with scope, ticket, file, workspace and repo', auto.includes('trailhead-merge-ticket.js add --scope <map-<n>|whiteboard> --ticket <n> --file <path> [--file <path> ...] --workspace <path> --repo <owner/name>'));
|
|
16
|
+
ok('auto.md: one --file per conflicting path of the merge result', /one `--file` per path in the merge result's `conflicts`/.test(auto));
|
|
17
|
+
ok('auto.md: the add runs right after the abort, before parking', /right after the abort, before parking/i.test(auto));
|
|
18
|
+
ok('auto.md: the park reason names the branch, workspace, files and the merge ticket', /park reason names the branch, the workspace, the conflicting files and the merge ticket/.test(auto));
|
|
19
|
+
ok('auto.md: one merge ticket per map or the whiteboard, opened at the first unmerged branch, updated after each later one', /one merge ticket per map \(or the whiteboard\)[^\n]*opened at the first unmerged branch[^\n]*updated after each later one/.test(auto));
|
|
20
|
+
ok('auto.md: never only at run end', /never only at run end/.test(auto));
|
|
21
|
+
ok('auto.md: a needs-you task so no run takes it, closed by the human', /needs-you `task`[^\n]*no run takes it[^\n]*closed by the human/.test(auto));
|
|
22
|
+
ok('auto.md: a map or the whiteboard with no unmerged branch gets no merge ticket', /no unmerged branch gets no merge ticket/.test(auto));
|
|
23
|
+
ok('auto.md: records as it goes names the merge ticket', /Records as it goes\n[^\n]*merge ticket/.test(auto));
|
|
24
|
+
ok('auto.md: run-end summary names the merge tickets', /\*\*what merged\*\*[^\n]*merge tickets/.test(auto));
|
|
25
|
+
ok('auto.md: a helper error is reported, named in the park reason, retried once at run end', /helper error[^\n]*reported in chat[^\n]*named in the park reason[^\n]*retried once at run end/.test(auto));
|
|
26
|
+
ok('auto.md: a still-failing retry is listed as no merge ticket written', /no merge ticket written/.test(auto));
|
|
27
|
+
ok('substrate-commands.md: names the helper and its call', commands.includes('trailhead-merge-ticket.js add --scope') && commands.includes('--workspace <path> --repo <owner/name>'));
|
|
28
|
+
ok('substrate-commands.md: finds through GraphQL OPEN with the marker', /trailhead:merge-ticket/.test(commands) && /states OPEN|OPEN issues/.test(commands));
|
|
29
|
+
ok('substrate-commands.md: an error is unknown, never none found', /merge ticket[^\n]*unknown, never as none found/.test(commands));
|
|
30
|
+
ok('no em-dash in auto.md', !auto.includes(String.fromCharCode(0x2014)));
|
|
31
|
+
ok('no em-dash in substrate-commands.md', !commands.includes(String.fromCharCode(0x2014)));
|
|
32
|
+
console.log(`auto-merge-ticket-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #284: the autonomous run asks the external review rounds at start-up and
|
|
3
|
+
// parks a ticket whose plan review does not converge. Plain asserts, no framework.
|
|
4
|
+
// Run: node bin/auto-review-rounds-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
|
|
10
|
+
const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
|
|
11
|
+
let passed = 0;
|
|
12
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
13
|
+
|
|
14
|
+
const auto = read('skills/trailhead-work/references/auto.md');
|
|
15
|
+
const review = read('skills/_shared/techniques/plan-review.md');
|
|
16
|
+
const config = read('skills/_shared/configuration-reference.md');
|
|
17
|
+
|
|
18
|
+
ok('auto.md has the External review rounds bullet', auto.includes('**External review rounds.**'));
|
|
19
|
+
ok('auto.md names the ask call', auto.includes('trailhead-plan-review-rounds.js ask'));
|
|
20
|
+
ok('auto.md: skip when plan review is off or no independent reviewer CLI is installed', /`skip`[^.]*plan review off[^.]*no independent reviewer CLI/.test(auto));
|
|
21
|
+
ok('auto.md: one AskUserQuestion with the helper options, at most four, config Recommended first', /one `AskUserQuestion`[^.]*`options`[^.]*at most four[^.]*Recommended/.test(auto));
|
|
22
|
+
ok('auto.md names the five choices', /as in config, 1, 2, 3, or until convergence/.test(auto));
|
|
23
|
+
ok('auto.md: until convergence stops on no progress, hard ceiling 5', /no progress[^.]*hard ceiling 5/.test(auto));
|
|
24
|
+
ok('auto.md: numbered list of all five without a structured question tool', /full five as a numbered list/.test(auto));
|
|
25
|
+
ok('auto.md: an ask error hands back before any claim', /`error`[^.]*reports it and hands back before any claim/.test(auto));
|
|
26
|
+
ok('auto.md: the ask is the human\'s, no delegate, also under <n>', /no delegate[^.]*before any claim/.test(auto) && /also under `\/trailhead:auto <n>`/.test(auto));
|
|
27
|
+
ok('auto.md: the choice holds for every build ticket, each slot included', /holds for every `build` ticket of the run[^.]*slot/.test(auto));
|
|
28
|
+
ok('auto.md: the wired-separately aside is gone', !/wired separately/.test(auto));
|
|
29
|
+
ok('auto.md: names the parking trigger statuses', /`exhausted`[^.]*`no-progress`[^.]*`ceiling`/.test(auto));
|
|
30
|
+
ok('auto.md: parks after PLAN and PLAN-REVIEW are posted, quoting each concern\'s full text', /after the `PLAN` and `PLAN-REVIEW` comments are posted/.test(auto) && /full `text`[^.]*`surviving`[^.]*never just (its|the) key/.test(auto));
|
|
31
|
+
ok('auto.md: names the round call', auto.includes('trailhead-plan-review-rounds.js round'));
|
|
32
|
+
|
|
33
|
+
ok('auto.md: the ask helper warnings are printed in chat before the ask or skip', /non-empty `warnings`[\s\S]{0,200}printed in chat first, before the ask or the skip/.test(auto));
|
|
34
|
+
ok('plan-review.md: rounds come from config or the run start-up choice', /`config\.plan_review\.rounds`[^.]*autonomous run[^.]*start-up choice/.test(review));
|
|
35
|
+
ok('plan-review.md: calls the round helper and follows its decision', review.includes('trailhead-plan-review-rounds.js round') && /follows its decision/.test(review));
|
|
36
|
+
ok('plan-review.md: under an autonomous run the checkpoint is not posed, the ticket is parked', /autonomous run[^.]*checkpoint is not posed[\s\S]{0,400}trailhead-needs-you\.js park/.test(review));
|
|
37
|
+
ok('plan-review.md: the park reason lists the surviving blocking concerns', /reason listing the surviving blocking concerns/.test(review));
|
|
38
|
+
ok('plan-review.md: interactive behaviour unchanged', /interactive[^.]*unchanged/i.test(review));
|
|
39
|
+
|
|
40
|
+
ok('configuration-reference.md: plan_review.rounds notes the run ask override', /plan_review\.rounds[^\n]*autonomous run asks at start[^\n]*overrides the key for that run/.test(config));
|
|
41
|
+
|
|
42
|
+
for (const [name, text] of [['auto.md', auto], ['plan-review.md', review], ['configuration-reference.md', config]]) {
|
|
43
|
+
ok(`${name}: no em-dash`, !text.includes(String.fromCharCode(0x2014)));
|
|
44
|
+
}
|
|
45
|
+
console.log(`auto-review-rounds-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #295: the autonomous run persists its start-up choices and lists in
|
|
3
|
+
// .trailhead/auto-run.json so it survives a compaction. Plain asserts, no framework.
|
|
4
|
+
// Run: node bin/auto-run-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
|
|
10
|
+
const auto = fs.readFileSync(path.join(plugin, 'skills/trailhead-work/references/auto.md'), 'utf8');
|
|
11
|
+
const gitignore = fs.readFileSync(path.resolve(__dirname, '..', '.gitignore'), 'utf8');
|
|
12
|
+
let passed = 0;
|
|
13
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
14
|
+
|
|
15
|
+
ok('auto.md: read runs at engagement, before the asks', /trailhead-auto-run\.js read --repo <main-checkout>[^\n]*before (any|the) ask/i.test(auto));
|
|
16
|
+
ok('auto.md: found with sameSession true is reused without asking and announced', /`sameSession: true`[^\n]*reuse it without asking/.test(auto) && /announced in one chat line naming the reused scope, slots and review/.test(auto));
|
|
17
|
+
ok('auto.md: another session\'s record asks once to resume or start fresh', /`sameSession: false`[^\n]*run still active elsewhere[^\n]*ask once/.test(auto) && /no delegate option/.test(auto) && /replaces the three asks/.test(auto));
|
|
18
|
+
ok('auto.md: resume reloads the skip and unmerged lists', /reload the record's skip and unmerged lists/.test(auto));
|
|
19
|
+
ok('auto.md: an explicit <n> that differs starts fresh with start --replace', /explicit `\/trailhead:auto <n>`[^\n]*differs[^\n]*starts a fresh run[^\n]*start --replace/.test(auto));
|
|
20
|
+
ok('auto.md: corrupt is reported then start --replace', /`corrupt`[^\n]*start --replace[^\n]*plain `start` would answer `exists`/.test(auto));
|
|
21
|
+
ok('auto.md: start runs after the last ask and before anything is claimed', /trailhead-auto-run\.js start --repo <main-checkout>[^\n]*--scope[^\n]*--slots[^\n]*--review/.test(auto) && /right after the last start-up ask and before anything is claimed/.test(auto));
|
|
22
|
+
ok('auto.md: the record is gitignored', auto.includes('.trailhead/auto-run.json') && /\.gitignore/.test(auto) && /^\.trailhead\/auto-run\.json$/m.test(gitignore));
|
|
23
|
+
ok('auto.md: every skipped outcome is reported and never blocks', /Any `skipped` result[^\n]*one chat line[^\n]*the run continues/.test(auto) && /skipped `read` is treated as `absent`/.test(auto) && /safety net, not a gate/.test(auto));
|
|
24
|
+
ok('auto.md: skip runs on each skip-list add', /trailhead-auto-run\.js skip --repo <main-checkout> --ticket <n>/.test(auto) && /every time a ticket joins the run's skip list/i.test(auto));
|
|
25
|
+
ok('auto.md: unmerged is recorded right after the abort, before the tracker call', /trailhead-auto-run\.js unmerged --repo <main-checkout> --ticket <n> --branch trailhead\/t<n>[^\n]*--file/.test(auto) && /before any tracker call/.test(auto));
|
|
26
|
+
ok('auto.md: unmerged again with --merge-ticket after the add succeeds', /--merge-ticket <n>/.test(auto) && /after the merge-ticket `add` succeeds/.test(auto));
|
|
27
|
+
ok('auto.md: a crash or failed add still leaves the branch on the persisted list', /still leaves the branch on the persisted unmerged list/.test(auto));
|
|
28
|
+
const parallel = auto.slice(auto.indexOf('## Parallel slots'), auto.indexOf('## Parking a ticket'));
|
|
29
|
+
const outside = auto.replace(parallel, '');
|
|
30
|
+
ok('auto.md: a general compaction rule sits outside Parallel slots and holds whatever the slot count and sameSession', /After a context compaction mid-run[^\n]*whatever the slot count[^\n]*trailhead-auto-run\.js read --repo <main-checkout>[^\n]*whatever `sameSession` says[^\n]*without asking/.test(outside) && /scope, slots, review choice, skip and unmerged lists/.test(outside));
|
|
31
|
+
ok('auto.md: the orchestrator knows it is mid-run from its own context', /mid-run from its own context/.test(outside));
|
|
32
|
+
ok('auto.md: the sameSession ask applies only at engagement', /`sameSession` ask applies only at engagement/.test(auto) && /fresh `\/trailhead:auto` invocation/.test(auto));
|
|
33
|
+
ok('auto.md: no session id host note (Codex)', /`sameSession` is always false on a host with no session id \(Codex\)/.test(auto) && /re-invocation after a crash always gets the resume-or-fresh ask/.test(auto) && /mid-run compaction never does/.test(auto));
|
|
34
|
+
ok('auto.md: the slot table bullet points at the general rule', /slot table from `trailhead-auto-slot\.js list`[^\n]*general compaction rule/.test(parallel) && !/trailhead-auto-run\.js read/.test(parallel));
|
|
35
|
+
ok('auto.md: run end unites the in-memory lists with the record', /in-memory skip and unmerged lists[^\n]*united with the record's/.test(auto));
|
|
36
|
+
ok('auto.md: run end clears the record, then resets the work states', /trailhead-auto-run\.js clear --repo <main-checkout>[^\n]*then[^\n]*--auto false/.test(auto) && /Clearing first means a crash between the two/.test(auto));
|
|
37
|
+
ok('auto.md: a host interrupt writes nothing, so Esc leaves the crash signature', !/graceful interrupt clears it too/.test(auto) && !/a graceful interrupt clears the record/.test(auto) && /same on disk as a crash/.test(auto) && /signature of both an Esc and a crash/.test(auto));
|
|
38
|
+
ok('auto.md: after Esc the same session reuses the record, a new session is asked again', /Esc[^\n]*reuses the record/.test(auto) && /new session[^\n]*(asked again|resume-or-fresh)/.test(auto));
|
|
39
|
+
ok('auto.md: step 2 writes the missing PAUSED checkpoint and label before resuming a crash-signature ticket', /Reconstruct the missing checkpoint/.test(auto) && /reconstructed at resume/.test(auto) && /adds `trailhead:paused` \(after the comment, so a failed comment never leaves a label without a checkpoint\)/.test(auto) && /resumes the ticket through the `resume` engine, which drops the label/.test(auto) && /no second checkpoint/.test(auto));
|
|
40
|
+
ok('auto.md: no run-end summary at a host interrupt, the resumed run covers it', /not emitted at the interrupt/.test(auto) && /resumed run's own (run-end )?summary/.test(auto));
|
|
41
|
+
ok('auto.md: crash signature and idempotence are stated on the latest checkpoint', /whose latest checkpoint is not a `PAUSED` comment/.test(auto) && /skipped only when the latest checkpoint is already a `PAUSED` comment or the label is present/.test(auto));
|
|
42
|
+
ok('auto.md: a slot ticket at the resolve step is exempt from reconstruction', /work state step is `resolve`[^\n]*exempt from (the )?reconstruction/.test(auto));
|
|
43
|
+
ok('auto.md: reconstruction happens when the ticket is resumed, not for a waiting extra', /Reconstruction happens when the ticket is actually resumed[^\n]*untouched/.test(auto));
|
|
44
|
+
const docs = {
|
|
45
|
+
'README.md': 'README.md',
|
|
46
|
+
'README.it.md': 'README.it.md',
|
|
47
|
+
'workflow.md': 'site/src/content/docs/docs/workflow.md',
|
|
48
|
+
'commands/work.md': 'site/src/content/docs/docs/commands/work.md',
|
|
49
|
+
};
|
|
50
|
+
const root = path.resolve(__dirname, '..');
|
|
51
|
+
for (const [name, rel] of Object.entries(docs)) {
|
|
52
|
+
const t = fs.readFileSync(path.join(root, rel), 'utf8');
|
|
53
|
+
const it = name === 'README.it.md';
|
|
54
|
+
ok(`${name}: no interrupt-time PAUSED checkpoint promise`, !/(Stop\/Esc|Esc or Stop|Esc)[^\n]*(keeps its claim and gets|keeps its claim with|mantiene il claim e riceve)[^\n]*`PAUSED`/.test(t) && !/gets a `PAUSED` checkpoint naming/.test(t));
|
|
55
|
+
ok(`${name}: Esc writes nothing and the next auto writes the missing PAUSED checkpoint`, it
|
|
56
|
+
? /Esc[^\n]*non scrive nulla[^\n]*stessa traccia di un crash[^\n]*`\/trailhead:auto`[^\n]*checkpoint `PAUSED` mancante[^\n]*workspace e branch/.test(t) && /stessa sessione[^\n]*riusa[^\n]*nuova sessione[^\n]*chiede di nuovo/.test(t)
|
|
57
|
+
: /Esc[^\n]*writes nothing[^\n]*same trace as a crash[^\n]*`\/trailhead:auto`[^\n]*missing `PAUSED` checkpoint[^\n]*workspace and branch/.test(t) && /same session[^\n]*reuses[^\n]*new session[^\n]*asks again/.test(t));
|
|
58
|
+
}
|
|
59
|
+
ok('auto.md: no em-dash', !auto.includes(String.fromCharCode(0x2014)));
|
|
60
|
+
console.log(`auto-run-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #280: auto.md documents the run scope (asked at start, `<n>` skips),
|
|
3
|
+
// the queue order rule, the helper, the helper-error stop, and the report split.
|
|
4
|
+
// Run: node auto-scope-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const plugin = path.join(__dirname, '..', 'plugins', 'trailhead');
|
|
10
|
+
const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
|
|
11
|
+
|
|
12
|
+
let passed = 0;
|
|
13
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
14
|
+
|
|
15
|
+
const auto = read('skills/trailhead-work/references/auto.md');
|
|
16
|
+
for (const needle of [
|
|
17
|
+
'**Pick the run scope.**', 'trailhead-auto-queue.js', "trailhead-auto-queue.js scopes", '`AskUserQuestion`', 'one map, the whiteboard, or everything',
|
|
18
|
+
'`/trailhead:auto <n>`', 'without asking', 'no delegate option', 'before anything is claimed',
|
|
19
|
+
'trailhead-auto-queue.js queue --scope', '--skip', '`next`', 'whiteboard last', 'ascending map number', 'oldest workable ticket first',
|
|
20
|
+
'A helper error stops the run', '`drift`', '`resume`', '--map none',
|
|
21
|
+
'each touched map gets a comment with **only its own part**', 'the whiteboard part stays in chat only', 'a map the run never touched gets no comment',
|
|
22
|
+
]) ok(`auto.md names: ${needle}`, auto.includes(needle));
|
|
23
|
+
|
|
24
|
+
for (const rel of ['skills/trailhead-work/references/auto.md', 'skills/trailhead-work/SKILL.md', 'commands/auto.md', 'templates/trailhead-auto-queue.js']) {
|
|
25
|
+
ok(`${rel} has no em-dash`, !read(rel).includes('—'));
|
|
26
|
+
}
|
|
27
|
+
for (const rel of ['skills/trailhead-work/SKILL.md', 'commands/auto.md']) {
|
|
28
|
+
ok(`${rel} describes a chosen scope`, /run scope/.test(read(rel)));
|
|
29
|
+
ok(`${rel} no longer says "the map's frontier"`, !read(rel).includes("the map's frontier"));
|
|
30
|
+
}
|
|
31
|
+
ok('commands/auto.md keeps the [map] hint', read('commands/auto.md').includes('argument-hint: "[map]"'));
|
|
32
|
+
|
|
33
|
+
console.log(`auto-scope-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #283: the autonomous run asks for parallel slots and works each slot
|
|
3
|
+
// ticket in its own worktree. Plain asserts, no framework. Run: node bin/auto-slots-docs.test.js
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
|
|
9
|
+
const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
|
|
10
|
+
let passed = 0;
|
|
11
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
12
|
+
|
|
13
|
+
const auto = read('skills/trailhead-work/references/auto.md');
|
|
14
|
+
const work = read('skills/trailhead-work/SKILL.md');
|
|
15
|
+
const team = read('skills/_shared/teamwork.md');
|
|
16
|
+
|
|
17
|
+
ok('auto.md asks the slot count, 1 to 4, proposed 2', /Parallel slots: 1, 2, 3 or 4/.test(auto) && /\*\*2 proposed\*\*/.test(auto) && auto.includes('AskUserQuestion'));
|
|
18
|
+
ok('auto.md asks the slot count even with <n>', /`\/trailhead:auto <n>` skips only the scope ask: it \*\*still asks the slot count\*\*/.test(auto));
|
|
19
|
+
ok('auto.md slot ask is the human\'s, no delegate', /there is no delegate option, and it is asked before anything is claimed/.test(auto));
|
|
20
|
+
ok('auto.md names the open helper call with --base', auto.includes('trailhead-auto-slot.js open --repo <main-checkout> --ticket <n> --base <sha>'));
|
|
21
|
+
ok('auto.md names the list helper call', auto.includes('trailhead-auto-slot.js list --repo <main-checkout>'));
|
|
22
|
+
ok('auto.md handles clone-required', auto.includes('clone-required') && auto.includes('checkout -b trailhead/t<n> <base>'));
|
|
23
|
+
ok('auto.md writes sessionless slot work state', auto.includes('env -u CLAUDE_CODE_SESSION_ID node <templates>/trailhead-work-state.js set --repo <workspace>'));
|
|
24
|
+
ok('auto.md resumes a ticket in its own workspace', /resumed \*\*in that workspace\*\*, whatever the slot count/.test(auto) && auto.includes('left alone'));
|
|
25
|
+
ok('auto.md: a slot never writes the active-map marker', /A slot never writes the `\.trailhead\/active-map` marker/.test(auto) && /every map-scoped call[^.]*names the map explicitly/.test(auto));
|
|
26
|
+
ok('auto.md: isolation override above 1 slot, none and auto included', /Above 1 slot[^.]*a worktree whatever `isolation:` says\*\* \(`none` and `auto` included\)/.test(auto));
|
|
27
|
+
ok('auto.md: background dispatch, nothing inline', auto.includes('background subagent') && auto.includes('nothing runs inline'));
|
|
28
|
+
ok('auto.md: in-flight tickets join --skip when filling a slot', /in-flight ticket/.test(auto) && /--skip <csv>` with the run's skip list \*\*plus every in-flight ticket\*\*/.test(auto));
|
|
29
|
+
ok('auto.md: integration held under git: pr and for a clone slot, branch and workspace kept', /Integration stays held under `git: pr`, and for a clone slot/.test(auto) && /parked needs-you for integration/.test(auto) && /\*\*kept\*\*/.test(auto));
|
|
30
|
+
ok('auto.md: no interrupt-time PAUSED for slot tickets, resume reconstructs it per workspace, run end per workspace', !/every in-flight slot ticket\*\* gets its `PAUSED` checkpoint/.test(auto) && /naming the slot workspace path and branch/.test(auto) && auto.includes('each remaining workspace'));
|
|
31
|
+
|
|
32
|
+
ok('auto.md: every set on a slot workspace is sessionless', /every `set` on a slot workspace[^.]*`env -u CLAUDE_CODE_SESSION_ID`/.test(auto));
|
|
33
|
+
ok('auto.md: the run-end reset on a workspace is sessionless too', /env -u CLAUDE_CODE_SESSION_ID node <templates>\/trailhead-work-state\.js set --repo <workspace> --auto false/.test(auto));
|
|
34
|
+
ok('auto.md: open statuses explained (reentered clone as is, stale worktree parks needs-you)', /`reentered` clone is used as is/.test(auto) && /stale worktree[\s\S]{0,400}park it needs-you/.test(auto) && auto.includes('git worktree prune'));
|
|
35
|
+
|
|
36
|
+
for (const [name, text] of [['teamwork.md', team], ['SKILL.md', work]]) {
|
|
37
|
+
ok(`${name}: no longer says the run is always the current checkout`, !/sequential work, no concurrency|works tickets sequentially/.test(text));
|
|
38
|
+
ok(`${name}: names the worktree per ticket above one slot`, /current checkout with one parallel slot and to a worktree per ticket above one/.test(text));
|
|
39
|
+
}
|
|
40
|
+
ok('teamwork.md worktree bullet names the slot helper', team.includes('through the `trailhead-auto-slot.js` helper'));
|
|
41
|
+
|
|
42
|
+
for (const [name, text] of [['auto.md', auto], ['SKILL.md', work], ['teamwork.md', team]]) {
|
|
43
|
+
ok(`${name}: no em-dash`, !text.includes(String.fromCharCode(0x2014)));
|
|
44
|
+
}
|
|
45
|
+
console.log(`auto-slots-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #281: an autonomous run resolves a ticket through a UAT follow-up
|
|
3
|
+
// (found first, reused on resume), writes every record when it arises, and documents
|
|
4
|
+
// the hard-stop guarantee. Run: node auto-uat-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const plugin = path.join(__dirname, '..', 'plugins', 'trailhead');
|
|
10
|
+
const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
|
|
11
|
+
|
|
12
|
+
const DASH = String.fromCharCode(0x2014);
|
|
13
|
+
let passed = 0;
|
|
14
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
15
|
+
|
|
16
|
+
const AUTO = 'skills/trailhead-work/references/auto.md';
|
|
17
|
+
const ACCEPT = 'skills/_shared/techniques/acceptance-testing.md';
|
|
18
|
+
const ENGINES = 'skills/trailhead-work/references/ticket-engines.md';
|
|
19
|
+
const SUBSTRATE = 'skills/_shared/substrate-commands.md';
|
|
20
|
+
|
|
21
|
+
const auto = read(AUTO);
|
|
22
|
+
for (const needle of [
|
|
23
|
+
'trailhead-uat-followup.js find', '## Records as it goes', 'never the only record', 'UAT of:', 'trailhead:uat',
|
|
24
|
+
'A helper error is not a "none found"', 'Only an open follow-up is reused', 'closed ones never count',
|
|
25
|
+
'a crash in the instant between the claim and the work-state write', 'leftover work state', 'whose work state has `auto: true`', 'already names the same action', 'its own checklist is already the record', 'trailhead-work-state.js',
|
|
26
|
+
'loses nothing that has arisen', 'UAT follow-ups opened this run',
|
|
27
|
+
'each touched map gets a comment with **only its own part**', 'the whiteboard part stays in chat only', 'a map the run never touched gets no comment',
|
|
28
|
+
]) ok(`auto.md names: ${needle}`, auto.includes(needle));
|
|
29
|
+
ok('auto.md no longer sets a ticket aside for a human-only UAT step', !auto.includes('leaves that step incomplete and sets the ticket aside'));
|
|
30
|
+
ok('auto.md names the needs-you label (#282)', auto.includes('trailhead:needs-you'));
|
|
31
|
+
|
|
32
|
+
const accept = read(ACCEPT);
|
|
33
|
+
for (const needle of ['trailhead-uat-followup.js find', 'autonomous run', 'auto.md', 'opens no duplicate'])
|
|
34
|
+
ok(`acceptance-testing.md names: ${needle}`, accept.includes(needle));
|
|
35
|
+
|
|
36
|
+
const engines = read(ENGINES);
|
|
37
|
+
ok('ticket-engines.md names the autonomous exception in build and bug Resolve', engines.split('Under an autonomous run the guard takes option (b) itself').length - 1 >= 2);
|
|
38
|
+
|
|
39
|
+
const substrate = read(SUBSTRATE);
|
|
40
|
+
for (const needle of ['trailhead-uat-followup.js find --parent <n> --repo <owner/name>', '"reuse"', 'paginated GraphQL `repository.issues` query', 'lagging search index'])
|
|
41
|
+
ok(`substrate-commands.md names: ${needle}`, substrate.includes(needle));
|
|
42
|
+
|
|
43
|
+
for (const rel of [AUTO, ACCEPT, ENGINES, SUBSTRATE, 'templates/trailhead-uat-followup.js', 'hooks/lib/uat-followup.test.js'])
|
|
44
|
+
ok(`${rel} has no em-dash`, !read(rel).includes(DASH));
|
|
45
|
+
ok('this test file has no em-dash', !fs.readFileSync(__filename, 'utf8').includes(DASH));
|
|
46
|
+
|
|
47
|
+
console.log(`auto-uat-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test: il lookup di <templates> negli skill rispetta CLAUDE_CONFIG_DIR (npm install),
|
|
3
|
+
// quindi nessun percorso nudo ~/.claude/trailhead/templates; il candidato Codex resta.
|
|
4
|
+
// Plain asserts, no framework. Run: node bin/config-dir-templates-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(__dirname, '..');
|
|
10
|
+
const SKILLS = path.join(root, 'plugins/trailhead/skills');
|
|
11
|
+
let passed = 0;
|
|
12
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
13
|
+
|
|
14
|
+
const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
15
|
+
e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]);
|
|
16
|
+
// Radici doc che il motore legge: skill, agent e comandi
|
|
17
|
+
const DOC_ROOTS = ['skills', 'agents', 'commands'].map((d) => path.join(root, 'plugins/trailhead', d));
|
|
18
|
+
const files = DOC_ROOTS.flatMap(walk).filter((f) => f.endsWith('.md'));
|
|
19
|
+
|
|
20
|
+
// Ogni grafia nuda della home: ~ o $HOME (la forma buona ha `}` dopo .claude, quindi non combacia)
|
|
21
|
+
const BARE = /(?:~|\$HOME)\/\.claude\/trailhead\/templates/g;
|
|
22
|
+
const GOOD = '${CLAUDE_CONFIG_DIR:-~/.claude}/trailhead/templates/';
|
|
23
|
+
const CODEX = '~/.codex/skills/trailhead/templates/';
|
|
24
|
+
|
|
25
|
+
for (const f of files) {
|
|
26
|
+
const rel = path.relative(root, f);
|
|
27
|
+
ok(`${rel}: no bare ~/.claude/trailhead/templates`, !(fs.readFileSync(f, 'utf8').match(BARE)));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Siti noti: candidato Claude con CLAUDE_CONFIG_DIR, candidato Codex intatto
|
|
31
|
+
for (const rel of [
|
|
32
|
+
'trailhead-work/SKILL.md',
|
|
33
|
+
'_shared/techniques.md',
|
|
34
|
+
'_shared/teamwork.md',
|
|
35
|
+
'trailhead-manage/references/auditing.md',
|
|
36
|
+
]) {
|
|
37
|
+
const t = fs.readFileSync(path.join(SKILLS, rel), 'utf8');
|
|
38
|
+
ok(`${rel}: config-dir candidate present`, t.includes(GOOD));
|
|
39
|
+
ok(`${rel}: codex candidate untouched`, t.includes(CODEX));
|
|
40
|
+
}
|
|
41
|
+
const cfg = fs.readFileSync(path.join(SKILLS, '_shared/configuration-reference.md'), 'utf8');
|
|
42
|
+
ok('configuration-reference: statusline lookup uses config dir', cfg.includes(GOOD + 'statusline-trailhead.sh'));
|
|
43
|
+
|
|
44
|
+
// #319: l'install della statusline va nella config dir, non in ~/.claude nudo
|
|
45
|
+
const stl = cfg.split('\n').find((l) => l.includes('On **Enable**, install it')) || '';
|
|
46
|
+
ok('configuration-reference: statusline install line found', stl.length > 0);
|
|
47
|
+
ok('configuration-reference: statusline target and settings use config dir',
|
|
48
|
+
stl.includes('${CLAUDE_CONFIG_DIR:-~/.claude}/statusline-trailhead.sh') && stl.includes('${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json'));
|
|
49
|
+
ok('configuration-reference: statusline install has no bare ~/.claude target or $HOME command',
|
|
50
|
+
!/(?<!:-)~\/\.claude\/(settings\.json|statusline-trailhead\.sh)/.test(stl) && !stl.includes('$HOME/.claude'));
|
|
51
|
+
|
|
52
|
+
console.log(`config-dir-templates-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test: una decision chiusa porta il suo lavoro nei build (riga `Builds:`), il grill
|
|
3
|
+
// che risolve un ticket fa lo stesso, e l'audit segnala le decisioni chiuse senza build.
|
|
4
|
+
// Plain asserts, no framework. Run: node bin/decision-builds-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(__dirname, '..');
|
|
10
|
+
const read = (rel) => fs.readFileSync(path.join(root, rel), 'utf8');
|
|
11
|
+
let passed = 0;
|
|
12
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
13
|
+
const EM = String.fromCharCode(0x2014);
|
|
14
|
+
|
|
15
|
+
// Sezione dal suo heading fino al prossimo heading dello stesso livello (o superiore).
|
|
16
|
+
const section = (text, headingRe) => {
|
|
17
|
+
const m = headingRe.exec(text);
|
|
18
|
+
if (!m) return '';
|
|
19
|
+
const level = m[0].match(/^#+/)[0].length;
|
|
20
|
+
const rest = text.slice(m.index + m[0].length);
|
|
21
|
+
const next = new RegExp(`^#{1,${level}} `, 'm').exec(rest);
|
|
22
|
+
return next ? rest.slice(0, next.index) : rest;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const SK = 'plugins/trailhead/skills';
|
|
26
|
+
|
|
27
|
+
// Motore decision
|
|
28
|
+
const engines = read(`${SK}/trailhead-work/references/ticket-engines.md`);
|
|
29
|
+
const dec = section(engines, /^## `decision`.*$/m);
|
|
30
|
+
ok('decision engine: section found', dec.length > 0);
|
|
31
|
+
ok('decision engine: a build follow-through step', /Carry it into build/.test(dec));
|
|
32
|
+
ok('decision engine: records a `Builds:` line', dec.includes('`Builds:'));
|
|
33
|
+
ok('decision engine: allows `Builds: none` only with a reason', /`Builds: none \(<why>\)`/.test(dec));
|
|
34
|
+
ok('decision engine: links existing tickets or creates build tickets', /existing/i.test(dec) && /create-then-wire/.test(dec));
|
|
35
|
+
ok('decision engine: the human confirms the build split in the final confirmation', /final confirmation/i.test(dec));
|
|
36
|
+
ok('decision engine: no close without the line', /never `gh issue close`/i.test(dec));
|
|
37
|
+
ok('decision engine: explains the fog that vanishes', /Not yet specified/.test(dec));
|
|
38
|
+
ok('decision engine: no em-dash', !dec.includes(EM));
|
|
39
|
+
|
|
40
|
+
// Work the map
|
|
41
|
+
const work = read(`${SK}/trailhead-work/SKILL.md`);
|
|
42
|
+
const step5 = (work.match(/^5\. Record the resolution:.*$/m) || [''])[0];
|
|
43
|
+
ok('work step 5: decision close waits on the `Builds:` line', step5.includes('`Builds:') && /Carry it into build/.test(step5));
|
|
44
|
+
|
|
45
|
+
// Grill su un ticket
|
|
46
|
+
const chart = read(`${SK}/trailhead-chart/SKILL.md`);
|
|
47
|
+
const grill = (chart.match(/^Run a standalone \*\*Grilling\*\*.*$/m) || [''])[0];
|
|
48
|
+
ok('grill: resolving a decision ticket carries it into build', /Carry it into build/.test(grill) && grill.includes('`Builds:'));
|
|
49
|
+
|
|
50
|
+
// Audit
|
|
51
|
+
const audit = read(`${SK}/trailhead-manage/references/auditing.md`);
|
|
52
|
+
const light = (audit.match(/^\*\*Light check\*\*.*$/m) || [''])[0];
|
|
53
|
+
ok('audit light check: flags a closed decision with no build', /no build/i.test(light) && light.includes('`Builds:'));
|
|
54
|
+
ok('audit light check: an older decision counts a build that links it', /links? (it|back)/i.test(light));
|
|
55
|
+
const report = section(audit, /^## 8\. Report$/m);
|
|
56
|
+
ok('audit report: lists decisions with no build', /no build/i.test(report));
|
|
57
|
+
const props = section(audit, /^## 9\. Proposals.*$/m);
|
|
58
|
+
ok('audit proposals: open the missing build tickets on a yes', /No build/.test(props) && /build/.test(props) && /`Builds:`/.test(props));
|
|
59
|
+
|
|
60
|
+
// Docs utente
|
|
61
|
+
const readme = read('README.md');
|
|
62
|
+
ok('README: decision engine leaves build tickets', /`decision` diverges options then grills to choose, then opens the build tickets/.test(readme));
|
|
63
|
+
ok('README: audit names decisions with no build', /decisions closed with no build/i.test(readme));
|
|
64
|
+
const readmeIt = read('README.it.md');
|
|
65
|
+
ok('README.it: decision engine leaves build tickets', /`decision` dirama le opzioni poi fa grilling per scegliere, poi apre i ticket build/.test(readmeIt));
|
|
66
|
+
ok('README.it: audit names decisions with no build', /decisioni chiuse senza build/i.test(readmeIt));
|
|
67
|
+
const wf = read('site/src/content/docs/docs/workflow.md');
|
|
68
|
+
ok('workflow.md: decision engine leaves build tickets', /`decision` diverges options then grills to choose, then opens the build tickets/.test(wf));
|
|
69
|
+
const manage = read('site/src/content/docs/docs/commands/manage.md');
|
|
70
|
+
ok('manage.md: audit names decisions with no build', /closed with no build/i.test(manage));
|
|
71
|
+
for (const [n, t] of [['README.md', readme], ['README.it.md', readmeIt], ['workflow.md', wf], ['manage.md', manage]]) {
|
|
72
|
+
ok(`${n}: no em-dash`, !t.includes(EM));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`decision-builds-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test: il charting apre come ticket (bloccati se serve) le aree con una Question
|
|
3
|
+
// già scrivibile, lascia in fog solo il resto; dopo ogni risoluzione la fog viene
|
|
4
|
+
// riesaminata tutta; a frontier vuota si propone un gruppo di voci da graduare.
|
|
5
|
+
// Plain asserts, no framework. Run: node bin/fog-vs-ticket-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.resolve(__dirname, '..');
|
|
11
|
+
const read = (rel) => fs.readFileSync(path.join(root, rel), 'utf8');
|
|
12
|
+
let passed = 0;
|
|
13
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
14
|
+
const EM = String.fromCharCode(0x2014);
|
|
15
|
+
const line = (text, re) => (text.match(re) || [''])[0];
|
|
16
|
+
|
|
17
|
+
const SK = 'plugins/trailhead/skills';
|
|
18
|
+
|
|
19
|
+
// Charting: il criterio ticket vs fog
|
|
20
|
+
const charting = read(`${SK}/trailhead-chart/references/charting.md`);
|
|
21
|
+
const step4 = line(charting, /^4\. \*\*Create the tickets you can specify now\*\*.*$/m);
|
|
22
|
+
ok('charting step 4: names the ticket-or-fog test', /Ticket or fog/.test(step4));
|
|
23
|
+
ok('charting step 4: a writable Question makes a ticket even when blocked', /Question/.test(step4) && /blocked/.test(step4) && /not a reason to leave it in the fog/i.test(step4));
|
|
24
|
+
ok('charting step 4: fog only when the question cannot be phrased yet', /cannot phrase/i.test(step4));
|
|
25
|
+
ok('charting step 4: fog when an open decision could reshape the question, and the entry says which', /reshape/i.test(step4) && /names? (that|the) (decision|ticket)/i.test(step4));
|
|
26
|
+
ok('charting step 4: the 100 sub-issue cap is the only volume brake', /100/.test(step4));
|
|
27
|
+
ok('charting: no em-dash', !charting.includes(EM));
|
|
28
|
+
|
|
29
|
+
// Work the map: riesame di tutta la fog dopo ogni risoluzione
|
|
30
|
+
const work = read(`${SK}/trailhead-work/SKILL.md`);
|
|
31
|
+
const step6 = line(work, /^6\. Add newly-surfaced tickets.*$/m);
|
|
32
|
+
ok('work step 6: re-scans every fog entry', /every entry/i.test(step6) && /Not yet specified/.test(step6));
|
|
33
|
+
ok('work step 6: applies the charting test', /Ticket or fog/.test(step6));
|
|
34
|
+
ok('work step 6: proposes the graduations as one batch', /one batch/i.test(step6));
|
|
35
|
+
|
|
36
|
+
// Handoff a frontier vuota: un gruppo, non una voce
|
|
37
|
+
const handoff = read(`${SK}/_shared/session-handoff.md`);
|
|
38
|
+
const empty = line(handoff, /^\s*\*\*If the frontier is empty\*\*.*$/m);
|
|
39
|
+
ok('handoff: empty frontier proposes a batch of ripe fog entries', /batch/i.test(empty) && /Ticket or fog/.test(empty));
|
|
40
|
+
ok('handoff: no em-dash', !handoff.includes(EM));
|
|
41
|
+
|
|
42
|
+
// Docs utente
|
|
43
|
+
const docs = [
|
|
44
|
+
['README.md', read('README.md'), false],
|
|
45
|
+
['README.it.md', read('README.it.md'), true],
|
|
46
|
+
['concepts.md', read('site/src/content/docs/docs/concepts.md'), false],
|
|
47
|
+
];
|
|
48
|
+
for (const [n, t, it] of docs) {
|
|
49
|
+
ok(`${n}: fog no longer clears "one at a time"`, it ? !/una alla volta, finché non resta/.test(t) : !/ticket, one at a time, until nothing/.test(t));
|
|
50
|
+
ok(`${n}: a phrasable question is a ticket, blocked if needed`, it
|
|
51
|
+
? /domanda già formulabile[^.]*ticket[^.]*bloccat/i.test(t)
|
|
52
|
+
: /question you can already phrase[^.]*ticket[^.]*blocked/i.test(t));
|
|
53
|
+
ok(`${n}: no em-dash`, !t.includes(EM));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`fog-vs-ticket-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #301: every grilling close runs a holistic review pass before the final
|
|
3
|
+
// confirmation. The rule is stated once (Grilling technique); call-sites only point at it.
|
|
4
|
+
// Run: node grilling-holistic-review-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
|
|
10
|
+
const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
|
|
11
|
+
|
|
12
|
+
let passed = 0;
|
|
13
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
14
|
+
|
|
15
|
+
const ANCHOR = 'Holistic review before the final confirmation';
|
|
16
|
+
const DEFINITION = `**${ANCHOR}.**`;
|
|
17
|
+
|
|
18
|
+
const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
19
|
+
e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]);
|
|
20
|
+
|
|
21
|
+
// 1. the bold definition exists exactly once, in the Grilling technique
|
|
22
|
+
const defs = walk(skills).filter((f) => fs.readFileSync(f, 'utf8').includes(DEFINITION));
|
|
23
|
+
ok('definition appears in exactly one file', defs.length === 1);
|
|
24
|
+
ok('definition lives in grilling.md', defs[0] === path.join(skills, '_shared', 'techniques', 'grilling.md'));
|
|
25
|
+
const grilling = read('_shared/techniques/grilling.md');
|
|
26
|
+
ok('definition appears once', grilling.split(DEFINITION).length === 2);
|
|
27
|
+
|
|
28
|
+
// 2. the definition bullet carries the normative fragments
|
|
29
|
+
const lines = grilling.split('\n');
|
|
30
|
+
const rule = lines.find((l) => l.includes(DEFINITION));
|
|
31
|
+
for (const frag of [
|
|
32
|
+
'Once the frontier is empty and before the final confirmation',
|
|
33
|
+
'as a whole',
|
|
34
|
+
'against each other and against the destination',
|
|
35
|
+
'proposed amendments',
|
|
36
|
+
'non-working combinations',
|
|
37
|
+
'contradictions, gaps, choices that break each other or the destination',
|
|
38
|
+
'by question number',
|
|
39
|
+
'say so in one line and go straight to the confirmation',
|
|
40
|
+
'accept, amend or reject',
|
|
41
|
+
'reject keeps the choices as they were',
|
|
42
|
+
'consciously accepted risk',
|
|
43
|
+
'reopens that branch',
|
|
44
|
+
'the review runs again on the new set',
|
|
45
|
+
're-confirmed in one play-back',
|
|
46
|
+
'at most once more',
|
|
47
|
+
'Only then is the final set played back, confirmed and recorded',
|
|
48
|
+
'`AskUserQuestion` menu',
|
|
49
|
+
'without the delegate option',
|
|
50
|
+
]) ok(`rule carries: ${frag}`, rule.includes(frag));
|
|
51
|
+
|
|
52
|
+
// 3. the Stop bullet is followed by the review
|
|
53
|
+
const stop = lines.find((l) => l.startsWith('- **Stop only when the frontier is empty'));
|
|
54
|
+
ok('stop bullet found', !!stop);
|
|
55
|
+
ok('stop bullet references the anchor', stop.includes(ANCHOR));
|
|
56
|
+
ok('stop bullet scopes the hold to the final set', stop.includes('the final set is not confirmed or recorded before it has run'));
|
|
57
|
+
|
|
58
|
+
// 4. call-sites only point at the rule
|
|
59
|
+
const pointer = (name, text, startsWith) => {
|
|
60
|
+
const l = text.split('\n').find((x) => x.startsWith(startsWith));
|
|
61
|
+
ok(`${name} found`, !!l);
|
|
62
|
+
ok(`${name} references the anchor`, l.includes(ANCHOR));
|
|
63
|
+
ok(`${name} does not restate the rule`, !l.includes('accepted risk') && !l.includes('reject keeps'));
|
|
64
|
+
};
|
|
65
|
+
pointer('chart SKILL standalone grill', read('trailhead-chart/SKILL.md'), 'Run a standalone **Grilling**');
|
|
66
|
+
const engines = read('trailhead-work/references/ticket-engines.md');
|
|
67
|
+
pointer('decision Converge', engines, '2. **Converge.**');
|
|
68
|
+
pointer('build Discuss', engines, '1. **Discuss**');
|
|
69
|
+
ok('build Discuss records the set after the review', engines.split('\n').find((l) => l.startsWith('1. **Discuss**')).includes('after the review'));
|
|
70
|
+
const charting = read('trailhead-chart/references/charting.md');
|
|
71
|
+
pointer('charting Mode 1 step 3', charting, '3. **Create the map**');
|
|
72
|
+
pointer('charting Mode 1-bis step 2', charting, '2. **Name the destination of the remaining stretch.**');
|
|
73
|
+
pointer('charting Mode 1-bis step 4', charting, '4. **Map the frontier of the remainder**');
|
|
74
|
+
ok('charting Mode 1 step 3 covers the destination too', charting.split('\n').find((l) => l.startsWith('3. **Create the map**')).includes('step 1 destination'));
|
|
75
|
+
const choices = read('_shared/choices.md');
|
|
76
|
+
const row = choices.split('\n').find((l) => l.startsWith('| Grilling holistic review'));
|
|
77
|
+
ok('choices row found', !!row);
|
|
78
|
+
const cells = row.split('|').map((c) => c.trim());
|
|
79
|
+
ok('choices row delegate No', cells[2].startsWith('No'));
|
|
80
|
+
ok('choices row defer No', cells[3] === 'No');
|
|
81
|
+
const prose = choices.split('\n').find((l) => l.startsWith('**Prose / conversational call-sites'));
|
|
82
|
+
ok('prose paragraph references the anchor', prose.includes(ANCHOR));
|
|
83
|
+
|
|
84
|
+
// 5. no em-dash in the touched files
|
|
85
|
+
for (const rel of ['_shared/techniques/grilling.md', '_shared/choices.md', 'trailhead-chart/SKILL.md',
|
|
86
|
+
'trailhead-work/references/ticket-engines.md', 'trailhead-chart/references/charting.md'])
|
|
87
|
+
ok(`${rel} has no em-dash`, !read(rel).includes(String.fromCharCode(8212)));
|
|
88
|
+
ok('this test has no em-dash', !fs.readFileSync(__filename, 'utf8').includes(String.fromCharCode(8212)));
|
|
89
|
+
|
|
90
|
+
console.log(`grilling-holistic-review-docs.test.js: ${passed} checks passed`);
|
|
@@ -69,7 +69,14 @@ function convertToCodex(md) {
|
|
|
69
69
|
// 2. Claude's /clear -> Codex's /new.
|
|
70
70
|
out = out.replace(/\/clear\b/g, '/new');
|
|
71
71
|
|
|
72
|
-
//
|
|
72
|
+
// 3a. Espansione della config dir: CLAUDE_CONFIG_DIR -> CODEX_HOME per intero,
|
|
73
|
+
// prima dei path nudi (altrimenti resta CLAUDE_CONFIG_DIR con fallback ~/.codex).
|
|
74
|
+
out = out.replace(/\$\{CLAUDE_CONFIG_DIR:-(~|\$HOME)\/\.claude\}/g, '${CODEX_HOME:-$1/.codex}');
|
|
75
|
+
// Forme nude rimaste in prosa: $CLAUDE_CONFIG_DIR e ${CLAUDE_CONFIG_DIR} -> CODEX_HOME.
|
|
76
|
+
out = out.replace(/\$\{CLAUDE_CONFIG_DIR\}/g, '${CODEX_HOME}');
|
|
77
|
+
out = out.replace(/\$CLAUDE_CONFIG_DIR\b/g, '$CODEX_HOME');
|
|
78
|
+
|
|
79
|
+
// 3b. Paths: anchored forms first, then the bare form.
|
|
73
80
|
out = out.replace(/\$HOME\/\.claude\b/g, '$HOME/.codex');
|
|
74
81
|
out = out.replace(/~\/\.claude\b/g, '~/.codex');
|
|
75
82
|
out = out.replace(/(?<![A-Za-z0-9_\-.\/~$])\.claude\//g, '.codex/');
|
|
@@ -113,7 +120,7 @@ Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_inp
|
|
|
113
120
|
The engine's \`/clear\` is Codex's \`/new\` (start a fresh session). Command references in a handoff use the skill form (\`$trailhead work <n>\`).
|
|
114
121
|
|
|
115
122
|
## F. Lifecycle hooks (native Codex hooks)
|
|
116
|
-
Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and
|
|
123
|
+
Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard, body-guard and labels-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs (the five run in one process, a single \`trailhead-bash-guards.js --guards=commit,secret,install,body,labels\` registration, so a Bash call pays one start-up instead of five), secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, mockup-link-stop as a \`Stop\` hook (when your final reply asks to approve a mockup without carrying its link, it continues the turn so you rewrite the ask with the URL or file path in the prose), and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
|
|
117
124
|
|
|
118
125
|
## G. Design / UI mockups: artifacts -> stitch offer, else local disk
|
|
119
126
|
The \`artifacts\` mockup mode (a Design canvas Artifact built with Claude Code's \`Artifact\` tool; the legacy \`claude.ai/design\` value means the same) does not exist on Codex: there is no \`Artifact\` tool here. When \`config.design\` names it, follow the Prototype technique's fallback: **offer once to switch this project to \`design: stitch\`** (the Google Stitch MCP runs on Codex too), and only on decline produce a throwaway static HTML mockup next to the code (\`disk\`), link it from the ticket, and say why. Never try to create or edit an Artifact on Codex. \`config.design.approval\` still applies unchanged: \`explicit\` waits for the user's go-ahead before any UI code, \`auto\` surfaces the mockup and proceeds.
|
|
@@ -366,9 +373,9 @@ function codexVerbSkillPlan(codexHome, verbs) {
|
|
|
366
373
|
// --- codexHookEntries -----------------------------------------------------
|
|
367
374
|
// The guardrail hooks trailhead registers on Codex, as {event, matcher,
|
|
368
375
|
// command} records. Commands run the copied hook scripts under hooksScriptsDir
|
|
369
|
-
// via node, matching Claude's `node "<path>"` form. The
|
|
370
|
-
// (commit, secret, install, body) are ONE registration, the merged
|
|
371
|
-
// trailhead-bash-guards.js with `--guards=commit,secret,install,body` (a single
|
|
376
|
+
// via node, matching Claude's `node "<path>"` form. The five Bash guards
|
|
377
|
+
// (commit, secret, install, body, labels) are ONE registration, the merged
|
|
378
|
+
// trailhead-bash-guards.js with `--guards=commit,secret,install,body,labels` (a single
|
|
372
379
|
// node process per Bash call); secret-read is a separate PreToolUse hook. All
|
|
373
380
|
// PreToolUse guards can veto; the injection-scanner
|
|
374
381
|
// is a PostToolUse advisory; check-update runs at SessionStart. Reused by the
|
|
@@ -381,7 +388,7 @@ function codexVerbSkillPlan(codexHome, verbs) {
|
|
|
381
388
|
function codexHookEntries(hooksScriptsDir) {
|
|
382
389
|
const cmd = (name) => `node "${path.join(hooksScriptsDir, name)}"`;
|
|
383
390
|
return [
|
|
384
|
-
{ event: 'PreToolUse', matcher: 'Bash', command: `${cmd('trailhead-bash-guards.js')} --guards=commit,secret,install,body` },
|
|
391
|
+
{ event: 'PreToolUse', matcher: 'Bash', command: `${cmd('trailhead-bash-guards.js')} --guards=commit,secret,install,body,labels` },
|
|
385
392
|
{ event: 'PreToolUse', matcher: 'Read|Bash', command: cmd('trailhead-secret-read-guard.js') },
|
|
386
393
|
{ event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.js') },
|
|
387
394
|
{ event: 'Stop', matcher: '', command: cmd('trailhead-mockup-link-stop.js') },
|