@marcomigozzi/trailhead 0.16.1 → 0.18.0
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 +82 -25
- package/README.md +82 -25
- package/bin/base-preflight-docs.test.js +4 -1
- package/bin/bulk-cross-reference-docs.test.js +36 -0
- package/bin/capture-action.test.js +167 -0
- package/bin/capture-docs.test.js +32 -0
- package/bin/engine-route-docs.test.js +155 -0
- package/bin/facet-stamp-docs.test.js +79 -0
- package/bin/forced-mutation-docs.test.js +38 -0
- package/bin/grill-model-docs.test.js +155 -0
- package/bin/grilling-holistic-review-docs.test.js +7 -3
- package/bin/guard-axis-docs.test.js +69 -0
- package/bin/inbox-needs-info-docs.test.js +68 -0
- package/bin/lib/codex-projection.js +28 -19
- package/bin/lib/codex-projection.test.js +77 -20
- package/bin/lib/host-descriptor.js +1 -1
- package/bin/lib/host-descriptor.test.js +6 -0
- package/bin/needs-you-docs.test.js +9 -2
- package/bin/options-in-chat-docs.test.js +65 -0
- package/bin/pick-stress-test-docs.test.js +1 -1
- package/bin/pipeline-review-docs.test.js +118 -0
- package/bin/plan-review-budget-docs.test.js +63 -0
- package/bin/repo-target-docs.test.js +41 -0
- package/bin/report-status-docs.test.js +10 -2
- package/bin/seam-coverage-docs.test.js +46 -0
- package/bin/session-trust-docs.test.js +37 -0
- package/bin/standards-brief-docs.test.js +112 -0
- package/bin/surfaced-scope-docs.test.js +57 -0
- package/bin/test-once-docs.test.js +101 -0
- package/bin/test-run-docs.test.js +47 -0
- package/bin/trailhead.js +15 -8
- package/bin/trailhead.test.js +71 -17
- package/bin/unblock-docs.test.js +56 -0
- package/bin/yes-accepts-recommended-docs.test.js +54 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +2 -1
- package/plugins/trailhead/agents/trailhead-codebase-map.md +1 -1
- package/plugins/trailhead/agents/trailhead-grill.md +41 -0
- package/plugins/trailhead/agents/trailhead-plan.md +1 -1
- package/plugins/trailhead/agents/trailhead-verify.md +1 -0
- package/plugins/trailhead/hooks/lib/audit-walk.test.js +62 -0
- package/plugins/trailhead/hooks/lib/capture.test.js +439 -0
- package/plugins/trailhead/hooks/lib/checkout-find.test.js +195 -0
- package/plugins/trailhead/hooks/lib/checkout-in.test.js +83 -7
- package/plugins/trailhead/hooks/lib/dashboard.test.js +19 -0
- package/plugins/trailhead/hooks/lib/gh-auth-preflight.js +89 -0
- package/plugins/trailhead/hooks/lib/gh-auth-preflight.test.js +132 -0
- package/plugins/trailhead/hooks/lib/git-env-mod.test.js +55 -0
- package/plugins/trailhead/hooks/lib/git-env-spawn.test.js +73 -0
- package/plugins/trailhead/hooks/lib/git-env.test.js +51 -0
- package/plugins/trailhead/hooks/lib/global-config-dir.test.js +48 -0
- package/plugins/trailhead/hooks/lib/heredoc-mod.test.js +85 -0
- package/plugins/trailhead/hooks/lib/inbox-needs-info.test.js +179 -0
- package/plugins/trailhead/hooks/lib/inherit-session.js +17 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +165 -0
- package/plugins/trailhead/hooks/lib/model-defaults-review.js +22 -1
- package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +64 -1
- package/plugins/trailhead/hooks/lib/model-route.js +207 -0
- package/plugins/trailhead/hooks/lib/model-route.test.js +231 -0
- package/plugins/trailhead/hooks/lib/needs-you.test.js +45 -1
- package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +272 -0
- package/plugins/trailhead/hooks/lib/repo-target.test.js +256 -0
- package/plugins/trailhead/hooks/lib/report-status.test.js +56 -1
- package/plugins/trailhead/hooks/lib/statusline-git-env.test.js +47 -0
- package/plugins/trailhead/hooks/lib/test-run.test.js +757 -0
- package/plugins/trailhead/hooks/lib/trailhead-git-env.js +40 -0
- package/plugins/trailhead/hooks/mod/README.md +42 -17
- package/plugins/trailhead/hooks/mod/auto-run.ts +2 -1
- package/plugins/trailhead/hooks/mod/git-env.ts +31 -0
- package/plugins/trailhead/hooks/mod/heredoc.ts +79 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +3 -1
- package/plugins/trailhead/hooks/mod/map-pane.tsx +204 -49
- package/plugins/trailhead/hooks/mod/my-tickets.ts +76 -0
- package/plugins/trailhead/hooks/mod/repo-pin.ts +113 -9
- package/plugins/trailhead/hooks/mod/work-state-locate.ts +2 -1
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +28 -14
- package/plugins/trailhead/hooks/trailhead-check-update.js +20 -2
- package/plugins/trailhead/hooks/trailhead-check-update.test.js +60 -0
- package/plugins/trailhead/hooks/trailhead-labels-guard.js +2 -2
- package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +1 -1
- package/plugins/trailhead/hooks/trailhead-test-guard.js +232 -0
- package/plugins/trailhead/hooks/trailhead-test-guard.test.js +172 -0
- package/plugins/trailhead/skills/_shared/capture-core.md +123 -0
- package/plugins/trailhead/skills/_shared/choices.md +2 -2
- package/plugins/trailhead/skills/_shared/configuration-reference.md +30 -16
- package/plugins/trailhead/skills/_shared/configuration.md +1 -1
- package/plugins/trailhead/skills/_shared/load-first.md +2 -0
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +19 -6
- package/plugins/trailhead/skills/_shared/substrate.md +4 -3
- package/plugins/trailhead/skills/_shared/surfaced-scope.md +23 -0
- package/plugins/trailhead/skills/_shared/teamwork.md +11 -5
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/code-review-pipeline.md +24 -0
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +22 -6
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +6 -3
- package/plugins/trailhead/skills/_shared/techniques/debug.md +5 -2
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +28 -3
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +16 -4
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/tdd.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/verify.md +5 -1
- package/plugins/trailhead/skills/_shared/techniques.md +8 -6
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +2 -4
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +29 -4
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -2
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +11 -6
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +18 -3
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +12 -9
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +3 -1
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +13 -13
- package/plugins/trailhead/templates/statusline-trailhead.sh +10 -3
- package/plugins/trailhead/templates/trailhead-audit-walk.js +56 -4
- package/plugins/trailhead/templates/trailhead-auto-merge.js +2 -1
- package/plugins/trailhead/templates/trailhead-auto-queue.js +2 -1
- package/plugins/trailhead/templates/trailhead-auto-slot.js +2 -1
- package/plugins/trailhead/templates/trailhead-base-preflight.js +10 -5
- package/plugins/trailhead/templates/trailhead-capture.js +419 -0
- package/plugins/trailhead/templates/trailhead-checkout-find.js +257 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +78 -21
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +2 -1
- package/plugins/trailhead/templates/trailhead-dashboard.js +15 -2
- package/plugins/trailhead/templates/trailhead-git-env.js +40 -0
- package/plugins/trailhead/templates/trailhead-inbox-needs-info.js +161 -0
- package/plugins/trailhead/templates/trailhead-isolation-answer.js +2 -1
- package/plugins/trailhead/templates/trailhead-isolation-copy.js +9 -4
- package/plugins/trailhead/templates/trailhead-issue-body.js +161 -12
- package/plugins/trailhead/templates/trailhead-merge-ticket.js +2 -1
- package/plugins/trailhead/templates/trailhead-needs-you.js +38 -4
- package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +261 -7
- package/plugins/trailhead/templates/trailhead-repo-target.js +302 -0
- package/plugins/trailhead/templates/trailhead-report-status.js +57 -9
- package/plugins/trailhead/templates/trailhead-test-run.js +752 -0
- package/plugins/trailhead/templates/trailhead-uat-backfill.js +2 -1
- package/plugins/trailhead/templates/trailhead-uat-followup.js +2 -1
- package/plugins/trailhead/templates/trailhead-uat-setup.js +9 -4
- package/plugins/trailhead/templates/trailhead-unblock.js +2 -1
- package/plugins/trailhead/tests/auto-run-ui.test.ts +2 -1
- package/plugins/trailhead/tests/git-env.test.ts +20 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +386 -50
- package/plugins/trailhead/tests/map-pane-view.test.ts +6 -0
- package/plugins/trailhead/tests/map-pane-write.test.ts +57 -1
- package/plugins/trailhead/tests/my-tickets.test.ts +66 -0
- package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +3 -2
- package/plugins/trailhead/tests/repo-pin-relocate.test.ts +175 -0
- package/plugins/trailhead/tests/repo-pin.test.ts +20 -5
- package/plugins/trailhead/tests/unscrub.ts +8 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +3 -2
- package/plugins/trailhead/tests/work-state-worktree.test.ts +2 -1
- package/plugins/trailhead/types/index.d.ts +7 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test per #325: ogni step del motore decide inline vs dispatch con
|
|
3
|
+
// model-route.js, non con la regola in prosa. Il testo canonico vive una volta
|
|
4
|
+
// in techniques.md; ogni punto di dispatch ha un gate imperativo che lo richiama.
|
|
5
|
+
// Run: node engine-route-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.join(__dirname, '..');
|
|
11
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
12
|
+
const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
16
|
+
|
|
17
|
+
const SECTION = '**Route every step through model-route.js.**';
|
|
18
|
+
const SECTION_NAME = 'Route every step through model-route.js';
|
|
19
|
+
|
|
20
|
+
// --- Parte A: la sezione canonica in techniques.md -----------------------------------
|
|
21
|
+
{
|
|
22
|
+
const techniques = read('_shared/techniques.md');
|
|
23
|
+
const at = techniques.indexOf(SECTION);
|
|
24
|
+
ok('A: techniques.md has the canonical routing paragraph', at !== -1);
|
|
25
|
+
const sec = at === -1 ? '' : techniques.slice(at, techniques.indexOf('\n\n', at));
|
|
26
|
+
const has = (...needles) => needles.every((n) => sec.includes(n));
|
|
27
|
+
ok('A: the command with every flag',
|
|
28
|
+
has('model-route.js <key> --session <your full model id>', '--session-effort', '--host codex', '--no-subagents', '<project root>'));
|
|
29
|
+
ok('A: the two-path <hooks> lookup', has('../../hooks/lib/', '../trailhead/hooks/lib/', 'model-defaults-review.js'));
|
|
30
|
+
ok('A: where the model id comes from', has('The exact model ID is', 'pass `unknown` only if it is truly absent'));
|
|
31
|
+
ok('A: the key per step', has(
|
|
32
|
+
'Plan -> `plan`', 'Execute -> `execute`', 'Fix -> `execute`', 'Code review -> `review`',
|
|
33
|
+
'Goal-backward verification -> `verify`', 'Research -> `research`', 'Debug -> `debug`',
|
|
34
|
+
'Codebase map -> `codebase-map`', 'Grilling -> `grill`'));
|
|
35
|
+
ok('A: Fix spawns trailhead-fix in place of the returned agent', has('spawn `trailhead-fix` in place of the returned `agent`'));
|
|
36
|
+
ok('A: Grilling keeps its own section', has('**Who runs a round**'));
|
|
37
|
+
ok('A: route inline, suggest and warning', has('`route: inline`', '`suggest`', '`warning`'));
|
|
38
|
+
ok('A: route dispatch uses agent and alias verbatim', has('`route: dispatch`', 'use its `agent` and `alias` verbatim'));
|
|
39
|
+
ok('A: no hand decision', has('Never decide inline versus dispatch by hand', 'never read the config yourself'));
|
|
40
|
+
ok('A: an unregistered agent runs inline', has('not registered', 'inline'));
|
|
41
|
+
ok('A: the lean exception', has('accepted `lean` skip'));
|
|
42
|
+
ok('A: the autonomous-run exception dispatches with no model override on an inline route',
|
|
43
|
+
has('more than one slot', 'no model override'));
|
|
44
|
+
ok('A: Codex spawns by agent_type, with a null alias', has('`agent_type=<agent>` on Codex', '`alias` is null'));
|
|
45
|
+
ok('A: Claude Code passes alias as the Agent model override', has('`alias` as the `Agent` model override on Claude Code'));
|
|
46
|
+
const rule = sec.indexOf('Never decide inline versus dispatch by hand');
|
|
47
|
+
const lean = sec.indexOf('accepted `lean` skip');
|
|
48
|
+
const auto = sec.indexOf('more than one slot');
|
|
49
|
+
ok('A: both exclusions are stated after the rule, inside the paragraph',
|
|
50
|
+
rule !== -1 && lean > rule && auto > rule && sec.indexOf('Two cases sit outside the route') > rule);
|
|
51
|
+
ok('A: the helper still runs in the autonomous case', sec.slice(auto).includes('the helper is still run'));
|
|
52
|
+
const dispatch = techniques.split('\n')[4] || '';
|
|
53
|
+
ok('A: the dispatch paragraph points at the routing paragraph', dispatch.includes(SECTION_NAME));
|
|
54
|
+
ok('A: the dispatch paragraph no longer restates the hand rule',
|
|
55
|
+
!dispatch.includes('running inline instead only when') && !dispatch.includes('Keep the inline-vs-subagent decision on the full id'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --- Parte B: il gate a ogni punto di dispatch ---------------------------------------
|
|
59
|
+
const gateOk = (text, key) =>
|
|
60
|
+
text.includes(`model-route.js ${key} --session <your full model id> <project root>`) &&
|
|
61
|
+
text.includes('obey its `route`') &&
|
|
62
|
+
text.includes('never decide inline versus dispatch by hand') &&
|
|
63
|
+
text.includes(SECTION_NAME);
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
const engines = read('trailhead-work/references/ticket-engines.md');
|
|
67
|
+
const lines = engines.split('\n');
|
|
68
|
+
const line = (prefix) => lines.find((l) => l.startsWith(prefix)) || '';
|
|
69
|
+
ok('B: build Plan gates on plan', gateOk(line('2. **Plan**:'), 'plan'));
|
|
70
|
+
ok('B: build Execute Dispatch gates on execute', gateOk(line(' - **Dispatch.** Spawn the **`trailhead-executor`**'), 'execute'));
|
|
71
|
+
ok('B: bug Diagnose gates on debug', gateOk(line('2. **Diagnose**:'), 'debug'));
|
|
72
|
+
const fix = line(' - **Dispatch.** Spawn the **`trailhead-fix`**');
|
|
73
|
+
ok('B: bug Fix Dispatch gates on execute and spawns trailhead-fix', gateOk(fix, 'execute') && fix.includes('`trailhead-fix`'));
|
|
74
|
+
ok('B: ticket-engines.md no longer restates the hand rule', !engines.includes('Inline only when the resolved model'));
|
|
75
|
+
const GATE = '**First, route it.**';
|
|
76
|
+
for (const [name, l] of [['Plan', line('2. **Plan**:')], ['Execute', line(' - **Dispatch.** Spawn the **`trailhead-executor`**')],
|
|
77
|
+
['Diagnose', line('2. **Diagnose**:')], ['Fix', fix]]) {
|
|
78
|
+
ok(`B: ${name} spawns by agent_type on Codex`, l.includes('agent_type='));
|
|
79
|
+
const g = l.indexOf(GATE);
|
|
80
|
+
ok(`B: ${name} gate comes before any lean-skip or exception wording`, g !== -1 && (l.indexOf('`lean`') === -1 || g < l.indexOf('`lean`')));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const [file, key, agent] of [
|
|
85
|
+
['code-review.md', 'review', 'trailhead-code-review'],
|
|
86
|
+
['verify.md', 'verify', 'trailhead-verify'],
|
|
87
|
+
['research.md', 'research', 'trailhead-research'],
|
|
88
|
+
['debug.md', 'debug', 'trailhead-debug'],
|
|
89
|
+
['codebase-map.md', 'codebase-map', 'trailhead-codebase-map'],
|
|
90
|
+
]) {
|
|
91
|
+
const text = read(`_shared/techniques/${file}`);
|
|
92
|
+
const gateAt = text.indexOf('**First, route it.**');
|
|
93
|
+
const dispatchAt = text.indexOf(`**\`${agent}\`** agent`);
|
|
94
|
+
const gate = gateAt === -1 ? '' : text.slice(gateAt, text.indexOf('\n', gateAt));
|
|
95
|
+
ok(`B: ${file} has a routing gate before its dispatch sentence`, gateAt !== -1 && dispatchAt !== -1 && gateAt < dispatchAt);
|
|
96
|
+
ok(`B: ${file} gate runs model-route.js ${key}`, gateOk(gate, key));
|
|
97
|
+
ok(`B: ${file} gate exempts the dispatched ${agent}`, gate.includes(`If you are the \`${agent}\` agent, skip this gate`));
|
|
98
|
+
ok(`B: ${file} no longer restates the hand rule`, !text.includes('inline only when the resolved model'));
|
|
99
|
+
ok(`B: ${file} gate spawns by agent_type on Codex`, gate.includes('agent_type=<agent>'));
|
|
100
|
+
ok(`B: ${file} gate points at the canonical paragraph`, gate.includes('Route every step through model-route.js'));
|
|
101
|
+
ok(`B: ${file} gate comes before any lean-skip wording`, text.indexOf('`lean`') === -1 || gateAt < text.indexOf('`lean`'));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- Parte B2: auto.md ---------------------------------------------------------------
|
|
105
|
+
{
|
|
106
|
+
const auto = read('trailhead-work/references/auto.md');
|
|
107
|
+
const line = auto.split('\n').find((l) => l.startsWith('- **Background dispatch, never inline.**')) || '';
|
|
108
|
+
ok('B: auto.md still dispatches every step in the background', line.includes('background subagent'));
|
|
109
|
+
ok('B: auto.md runs the helper per step', line.includes('model-route.js <key> --session <your full model id> <project root>') && line.includes('Route every step through model-route.js'));
|
|
110
|
+
ok('B: auto.md spawns with no model override on an inline route', line.includes('`route: inline`') && line.includes('no model override'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- Parte B3: rifiniture della review di #325 --------------------------------------
|
|
114
|
+
{
|
|
115
|
+
const cr = read('_shared/techniques/code-review.md');
|
|
116
|
+
ok('B: code-review.md no longer says a lean skip is the only inline review', !cr.includes('only inline review'));
|
|
117
|
+
ok('B: code-review.md says the review runs inline on route: inline', cr.includes('in the session on `route: inline`'));
|
|
118
|
+
const audit = read('trailhead-manage/references/auditing.md');
|
|
119
|
+
ok('B: auditing.md routes verify once per run through model-route.js',
|
|
120
|
+
audit.includes('routes `verify` once per run through `model-route.js`') && audit.includes('Route every step through model-route.js'));
|
|
121
|
+
const agent = fs.readFileSync(path.join(root, 'plugins', 'trailhead', 'agents', 'trailhead-codebase-map.md'), 'utf8');
|
|
122
|
+
ok('B: the codebase-map agent no longer claims it has no model key', !agent.includes('no dedicated model key') && agent.includes('config.models.codebase-map'));
|
|
123
|
+
const cfg = read('_shared/configuration.md');
|
|
124
|
+
const ref = read('_shared/configuration-reference.md');
|
|
125
|
+
ok('B: configuration docs say every key routes through model-route.js',
|
|
126
|
+
!cfg.includes('every model key runs as a subagent') && !ref.includes('Every model key runs as a subagent') &&
|
|
127
|
+
cfg.includes('every model key routes through `model-route.js`') && ref.includes('Every model key routes through `model-route.js`'));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Parte C: la config descrive l'helper, non una decisione a mano -------------------
|
|
131
|
+
{
|
|
132
|
+
const cfg = read('_shared/configuration.md');
|
|
133
|
+
const ref = read('_shared/configuration-reference.md');
|
|
134
|
+
ok('C: configuration.md lets the helper decide', cfg.includes('model-route.js') && !cfg.includes('Run Execute inline only when'));
|
|
135
|
+
const bullet = (k) => ref.split('\n').find((l) => l.startsWith(`- \`${k}\` →`)) || '';
|
|
136
|
+
ok('C: configuration-reference.md plan bullet points at the helper', bullet('plan').includes('model-route.js') && !bullet('plan').includes('inline only when'));
|
|
137
|
+
ok('C: configuration-reference.md execute bullet points at the helper', bullet('execute').includes('model-route.js') && !bullet('execute').includes('inline in the session instead when'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// --- Parte D: l'helper copre ogni chiave nominata ------------------------------------
|
|
141
|
+
{
|
|
142
|
+
const { AGENT_OF } = require(path.join(root, 'plugins', 'trailhead', 'hooks', 'lib', 'model-route.js'));
|
|
143
|
+
for (const key of ['plan', 'execute', 'review', 'verify', 'research', 'debug', 'codebase-map', 'grill']) {
|
|
144
|
+
ok(`D: model-route.js knows ${key} on both hosts`, !!AGENT_OF.claude[key] && !!AGENT_OF.codex[key]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- Parte E: #350, ogni agent del motore parte con contesto fresco, mai un fork ---------
|
|
149
|
+
{
|
|
150
|
+
const techniques = read('_shared/techniques.md');
|
|
151
|
+
ok('E: techniques.md states engine agents spawn with fresh context, never a fork',
|
|
152
|
+
/fresh context, never a fork/.test(techniques) && techniques.includes('fork_turns'));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`engine-route-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #360: every codebase facet is stamped with the commit it is true
|
|
3
|
+
// at, the Resolve patches of build and bug restamp the facet they touch, and the
|
|
4
|
+
// audit flags facets whose files moved after their stamp.
|
|
5
|
+
// Run: node facet-stamp-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.join(__dirname, '..');
|
|
11
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
12
|
+
const docs = path.join(root, 'site', 'src', 'content', 'docs', 'docs');
|
|
13
|
+
const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
|
|
14
|
+
|
|
15
|
+
let passed = 0;
|
|
16
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
17
|
+
|
|
18
|
+
const map = read(skills, '_shared', 'techniques', 'codebase-map.md');
|
|
19
|
+
const substrate = read(skills, '_shared', 'substrate.md');
|
|
20
|
+
const engines = read(skills, 'trailhead-work', 'references', 'ticket-engines.md');
|
|
21
|
+
const auditing = read(skills, 'trailhead-manage', 'references', 'auditing.md');
|
|
22
|
+
const manage = read(docs, 'commands', 'manage.md');
|
|
23
|
+
const concepts = read(docs, 'concepts.md');
|
|
24
|
+
const readmes = [['README.md', read(root, 'README.md')], ['README.it.md', read(root, 'README.it.md')]];
|
|
25
|
+
|
|
26
|
+
// The format and who writes it: codebase-map.md.
|
|
27
|
+
ok('map: facet stamp format', map.includes('`_as of <short sha>_`'));
|
|
28
|
+
ok('map: stamp comes from the scan root HEAD', map.includes('`git -C <scan root> rev-parse --short HEAD`'));
|
|
29
|
+
ok('map: a full re-run restamps every facet', /full re-run[^.]*restamps? (?:all|every)/i.test(map));
|
|
30
|
+
ok('map: the restamp rule is not a parenthetical splitting the sentence', !/restamps all of them\)/.test(map));
|
|
31
|
+
ok('map: a patch passes --stamp', map.includes('--section "<facet>" --stamp <short sha>'));
|
|
32
|
+
ok('map: an unstamped facet gains it on its next patch', /no stamp[^.]*gains it on its next patch/i.test(map));
|
|
33
|
+
ok('map: an untouched facet keeps its stamp', /untouched[^.]*keeps its (?:old )?stamp/i.test(map));
|
|
34
|
+
|
|
35
|
+
// The substrate row states the stamp.
|
|
36
|
+
const codebaseRow = substrate.split('\n').find((l) => l.startsWith('| Codebase |')) || '';
|
|
37
|
+
ok('substrate: Codebase row states the stamp', codebaseRow.includes('`_as of <short sha>_`'));
|
|
38
|
+
|
|
39
|
+
// Resolve of build and bug.
|
|
40
|
+
const resolves = engines.split('\n').filter((l) => l.startsWith('5. **Resolve**'));
|
|
41
|
+
ok('engines: build and bug Resolve both found', resolves.length === 2);
|
|
42
|
+
for (const [name, line] of [['build', resolves[0] || ''], ['bug', resolves[1] || '']]) {
|
|
43
|
+
ok(`engines ${name}: the facet patch passes --stamp`, line.includes('--stamp'));
|
|
44
|
+
ok(`engines ${name}: stamp is the trunk tip after integration`, /trunk tip/.test(line));
|
|
45
|
+
ok(`engines ${name}: a pr not merged yet stamps the fork point`, line.includes('git merge-base HEAD origin/<trunk>'));
|
|
46
|
+
ok(`engines ${name}: by-hand fallback updates the stamp line`, /by hand[^.]*stamp line/i.test(line));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The audit.
|
|
50
|
+
const secStart = auditing.indexOf('## 7b. Codebase facets');
|
|
51
|
+
const secEnd = auditing.indexOf('## 8. Report');
|
|
52
|
+
const sec = secStart >= 0 ? auditing.slice(secStart, secEnd) : '';
|
|
53
|
+
ok('auditing: 7b sits between 7 and 8', secStart > auditing.indexOf('## 7. Compare the open tickets') && secEnd > secStart);
|
|
54
|
+
ok('auditing 7b: runs the script facets command', sec.includes('trailhead-audit-walk.js facets --repo'));
|
|
55
|
+
ok('auditing 7b: whole-repo runs only', /whole-repo/.test(sec) && /\[map\]/.test(sec));
|
|
56
|
+
ok('auditing 7b: no codebase issue skips it', /no `trailhead:codebase` issue/.test(sec));
|
|
57
|
+
ok('auditing 7b: singleton fail-closed lookup', /fail-closed/.test(sec));
|
|
58
|
+
ok('auditing 7b: body in a private mktemp directory removed on every path', sec.includes('mktemp -d') && /every path/.test(sec));
|
|
59
|
+
ok('auditing 7b: a script failure never blocks the audit or the marker', /never blocks the audit or the marker/.test(sec));
|
|
60
|
+
for (const status of ['current', 'stale', 'unstamped', 'unresolved', 'no-paths']) {
|
|
61
|
+
ok(`auditing 7b: status ${status}`, sec.includes(`\`${status}\``));
|
|
62
|
+
}
|
|
63
|
+
ok('auditing 7b: current is not proof either, a patch moves the stamp over the whole facet', /`current` is not proof/.test(sec) && /whole facet/.test(sec));
|
|
64
|
+
const report = auditing.slice(secEnd, auditing.indexOf('## 9. Proposals'));
|
|
65
|
+
ok('auditing 8: report lists stale facets with changed files, outside the sum', /stale facets?[^.]*changed files/i.test(report) && /outside the (?:class )?sum/.test(report));
|
|
66
|
+
const proposals = auditing.slice(auditing.indexOf('## 9. Proposals'), auditing.indexOf('## 10.'));
|
|
67
|
+
ok('auditing 9: stale facets get no proposal', /\*\*Stale facets\*\* → no proposal/.test(proposals));
|
|
68
|
+
|
|
69
|
+
// Reader-facing docs.
|
|
70
|
+
ok('site manage: audit mentions stale codebase facets', /codebase facet/i.test(manage) && /as of/.test(manage));
|
|
71
|
+
ok('site concepts: codebase bullet mentions the stamp', concepts.split('\n').some((l) => l.includes('`trailhead:codebase`') && /as of/.test(l)));
|
|
72
|
+
|
|
73
|
+
for (const [name, text] of readmes) {
|
|
74
|
+
const lines = text.split('\n');
|
|
75
|
+
ok(`${name}: codebase bullet mentions the stamp`, lines.some((l) => l.includes('`trailhead:codebase`') && l.includes('`_as of <short sha>_`')));
|
|
76
|
+
ok(`${name}: audit paragraph mentions stale codebase facets`, lines.some((l) => l.startsWith('`/trailhead:audit') && l.includes('`_as of <short sha>_`')));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`facet-stamp-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #357: a red obtained by forcing a change (mutating code or a
|
|
3
|
+
// fixture) counts only after the change is shown to have landed, stated by the
|
|
4
|
+
// RED step of TDD and by the "watch it fail" step of Debug.
|
|
5
|
+
// Run: node forced-mutation-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const techniques = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills', '_shared', 'techniques');
|
|
11
|
+
const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
|
|
12
|
+
|
|
13
|
+
let passed = 0;
|
|
14
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
15
|
+
|
|
16
|
+
const RULE = 'prove the change landed before trusting the run: diff the mutated file against a clean copy, or re-read it, and see the mutation there';
|
|
17
|
+
const WHY = 'A mutation that silently failed to apply leaves the code as it was, so the run proves nothing';
|
|
18
|
+
|
|
19
|
+
const tdd = read(techniques, 'tdd.md');
|
|
20
|
+
const debug = read(techniques, 'debug.md');
|
|
21
|
+
|
|
22
|
+
const red = tdd.split('\n').find((l) => l.startsWith('- **RED**:')) || '';
|
|
23
|
+
ok('tdd: the RED step carries the rule', red.includes(RULE));
|
|
24
|
+
ok('tdd: the RED step says why', red.includes(WHY));
|
|
25
|
+
|
|
26
|
+
const verifyFix = debug.split('\n').find((l) => l.startsWith('9. **Verify the fix.**')) || '';
|
|
27
|
+
ok('debug: the rule follows "watch it fail"', verifyFix.indexOf(RULE) > verifyFix.indexOf('Watch it fail') && verifyFix.includes('Watch it fail'));
|
|
28
|
+
ok('debug: the step says why', verifyFix.includes(WHY));
|
|
29
|
+
|
|
30
|
+
for (const [name, text] of Object.entries({
|
|
31
|
+
'tdd.md': tdd,
|
|
32
|
+
'debug.md': debug,
|
|
33
|
+
'forced-mutation-docs.test.js': read(__dirname, 'forced-mutation-docs.test.js'),
|
|
34
|
+
})) {
|
|
35
|
+
ok(`${name} has no em-dash`, !text.includes(String.fromCharCode(0x2014)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`forced-mutation-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test per #323: Grilling gira su models.grill / trailhead-grill, con la sessione
|
|
3
|
+
// principale come orchestratore HITL. Il protocollo e' scritto una volta (grilling.md);
|
|
4
|
+
// i punti d'ingresso lo richiamano soltanto.
|
|
5
|
+
// Run: node grill-model-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.join(__dirname, '..');
|
|
11
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
12
|
+
const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
16
|
+
|
|
17
|
+
// --- Parte A: configuration-reference.md e configuration.md --------------------------
|
|
18
|
+
{
|
|
19
|
+
const ref = read('_shared/configuration-reference.md');
|
|
20
|
+
const cfg = read('_shared/configuration.md');
|
|
21
|
+
const lines = ref.split('\n');
|
|
22
|
+
const rowStart = (prefix) => lines.find((l) => l.startsWith(prefix)) || '';
|
|
23
|
+
ok('A: models.{...} key-table row lists grill', /^\| `models\.\{[^}]*\bgrill\b[^}]*\}`/.test(rowStart('| `models.{')));
|
|
24
|
+
ok('A: models.codex.{...} key-table row lists grill', /^\| `models\.codex\.\{[^}]*\bgrill\b[^}]*\}`/.test(rowStart('| `models.codex.{')));
|
|
25
|
+
ok('A: Models bullet for grill', lines.some((l) => /^- `grill`:/.test(l) && l.includes('Grilling')));
|
|
26
|
+
ok('A: subagent bullet maps grill to trailhead-grill and says the session keeps asking the user',
|
|
27
|
+
lines.some((l) => /^- `grill`/.test(l) && l.includes('trailhead-grill') && /session (keeps|still) ask/.test(l)));
|
|
28
|
+
const header = lines.find((l) => l.startsWith('| profile |')) || '';
|
|
29
|
+
ok('A: profile table has a grill column', header.includes('| grill |'));
|
|
30
|
+
const col = (name) => {
|
|
31
|
+
const idx = header.split('|').map((c) => c.trim()).indexOf('grill');
|
|
32
|
+
const row = lines.find((l) => l.startsWith(`| **${name}** |`)) || '';
|
|
33
|
+
return row.split('|').map((c) => c.trim())[idx];
|
|
34
|
+
};
|
|
35
|
+
ok('A: grill column reads High strong, Balanced strong, Low standard',
|
|
36
|
+
col('High') === 'strong' && col('Balanced') === 'strong' && col('Low') === 'standard');
|
|
37
|
+
ok('A: a profile writes no grill entry, so grill keeps following plan',
|
|
38
|
+
ref.includes('a profile writes **no `grill` entry**') && ref.includes('keeps following `plan`'));
|
|
39
|
+
ok('A: the key rows say an unset grill follows plan',
|
|
40
|
+
rowStart('| `models.{').includes('unset `grill` follows `plan`') && rowStart('| `models.codex.{').includes('unset `grill` follows `models.codex.plan`'));
|
|
41
|
+
ok('A: Models bullet says unset grill is the same model as plan', lines.some((l) => /^- `grill`:/.test(l) && l.includes('Unset, it uses the same model as `plan`')));
|
|
42
|
+
ok('A: the unset-default paragraph names the grill exception',
|
|
43
|
+
ref.includes('the one exception is `grill`: unset, it follows the resolved `models.plan`'));
|
|
44
|
+
ok('A: Manual path offers grill with "same as plan (Recommended)" first, leaving the key unset',
|
|
45
|
+
ref.includes('**same as plan (Recommended)** as the first option') && ref.includes('leaves `models.grill` unset') &&
|
|
46
|
+
ref.includes('leaves `models.codex.grill` unset'));
|
|
47
|
+
ok('A: a layer that sets plan but not grill implies grill = plan before the merge',
|
|
48
|
+
ref.includes('a config layer that sets `plan` but not `grill` implicitly sets `grill` to that `plan` for that layer, before the project-over-global merge'));
|
|
49
|
+
ok('A: existing configs without grill need no action or notice',
|
|
50
|
+
ref.includes('An existing config with no `grill` key grills on its `plan` model with no action and no notice'));
|
|
51
|
+
const advanced = lines.filter((l) => l.includes('behind an "Advanced" option') || l.includes('behind Advanced'));
|
|
52
|
+
ok('A: Advanced lists name grill for Claude and Codex', advanced.length === 2 && advanced.every((l) => l.includes('`grill`')));
|
|
53
|
+
const everything = lines.find((l) => l.startsWith('**Everything else runs in the main session')) || '';
|
|
54
|
+
ok('A: Everything else paragraph no longer lists grilling', everything.length > 0 && !/grilling/i.test(everything.split("That's deliberate")[0]));
|
|
55
|
+
ok('A: Codex sentence (unset inherits, profile or pin brings strong, equal pin runs inline)',
|
|
56
|
+
/unset `models\.codex\.grill` follows `models\.codex\.plan` when that is pinned/.test(ref) &&
|
|
57
|
+
ref.includes('else inherits the session model') && ref.includes('an equal pin (same model as the session'));
|
|
58
|
+
ok('A: a spawn gets the family, not the exact version', ref.includes('A dispatched grill is guaranteed the family of the resolved id, not its exact version'));
|
|
59
|
+
ok('A: configuration.md names the grill exception to the Balanced default', cfg.includes('except `grill`, which follows `plan`'));
|
|
60
|
+
ok('A: configuration.md lists grill in the subagent list', /`research`, `review`, `debug`, `verify`, `codebase-map`, `grill`/.test(cfg));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// --- Parte B: grilling.md "Who runs a round" e techniques.md ---------------------------
|
|
64
|
+
{
|
|
65
|
+
const grilling = read('_shared/techniques/grilling.md');
|
|
66
|
+
const techniques = read('_shared/techniques.md');
|
|
67
|
+
const at = grilling.indexOf('\n## Who runs a round');
|
|
68
|
+
ok('B: grilling.md has a Who runs a round section', at !== -1);
|
|
69
|
+
const sec = at === -1 ? '' : grilling.slice(at);
|
|
70
|
+
const has = (...needles) => needles.every((n) => sec.includes(n));
|
|
71
|
+
const gateAt = grilling.indexOf('**First, before any question: route the round.**');
|
|
72
|
+
const firstBullet = grilling.indexOf('\n- **Map the decision as a tree.**');
|
|
73
|
+
ok('B: the routing gate exists and precedes the first round bullet', gateAt !== -1 && firstBullet !== -1 && gateAt < firstBullet);
|
|
74
|
+
const gate = gateAt === -1 ? '' : grilling.slice(gateAt, firstBullet);
|
|
75
|
+
ok('B: the gate runs model-route.js grill --session with the session full model id and obeys route',
|
|
76
|
+
gate.includes('model-route.js grill --session <your full model id> <project root>') && gate.includes('obey its `route`'));
|
|
77
|
+
ok('B: the gate says where the model id comes from and when to pass unknown',
|
|
78
|
+
gate.includes('system prompt or environment') && gate.includes('The exact model ID is') && gate.includes('pass `unknown` only if it is truly absent'));
|
|
79
|
+
ok('B: the gate forbids hand decisions', gate.includes('Never decide inline versus dispatch by hand') && gate.includes('never skip the helper') && gate.includes('never read the config yourself'));
|
|
80
|
+
ok('B: on dispatch the session does not write the questions and relays the report',
|
|
81
|
+
gate.includes('you do not write the questions yourself') && gate.includes('spawn `agent` with model `alias`') && gate.includes('relay its report'));
|
|
82
|
+
ok('B: the gate points at Who runs a round and exempts the dispatched agent', gate.includes('**Who runs a round**') && gate.includes('If you are the `trailhead-grill` agent, skip this gate'));
|
|
83
|
+
ok('B: the route call, with --session-effort and unknown',
|
|
84
|
+
has('model-route.js grill --session', '--session-effort', 'unknown', '--no-subagents', '--host codex'));
|
|
85
|
+
ok('B: the helper is found with the same two-path lookup as model-defaults-review.js',
|
|
86
|
+
has('../../hooks/lib/', '../trailhead/hooks/lib/', 'model-defaults-review.js'));
|
|
87
|
+
ok('B: a context-window suffix on the session id is documented as ignored', has('a trailing `[...]` suffix such as `[1m]` is ignored'));
|
|
88
|
+
ok('B: the per-layer implicit grill rule', has('a config layer that sets `plan` but not `grill` implicitly sets `grill` to that `plan` for that layer, before the project-over-global merge'));
|
|
89
|
+
ok('B: an unset models.grill follows models.plan', has('`models.grill` unset follows `models.plan`'));
|
|
90
|
+
ok('B: use its agent and alias verbatim', has('use its `agent` and `alias` verbatim'));
|
|
91
|
+
ok('B: route inline with the suggest line', has('`route: inline`', '`suggest`', 'suggesting the user switch the session'));
|
|
92
|
+
ok('B: an unregistered agent runs inline', has('not registered', 'inline'));
|
|
93
|
+
ok('B: the report is checked with --technique grill', has('--technique grill'));
|
|
94
|
+
ok('B: the relay never answers on the user\'s behalf', has('relay', 'Never answer on the user\'s behalf', 'never proceed on silence'));
|
|
95
|
+
ok('B: re-dispatch with the full state', has('re-dispatch with the full state'));
|
|
96
|
+
ok('B: the orchestrator holds the review cap', has('The orchestrator caps reviews at two', 'accepted risk'));
|
|
97
|
+
ok('B: the apply/amend/keep menu is the orchestrator\'s', has('apply', 'amend', 'keep', 'AskUserQuestion'));
|
|
98
|
+
ok('B: the family-not-version note', has('the family of the resolved id, not its exact version'));
|
|
99
|
+
ok('B: an inline round writes no report', has('An inline round writes no report'));
|
|
100
|
+
ok('B: Codex semantics', has('unset `models.codex.grill` follows `models.codex.plan` when that is pinned', 'else inherits the session model', 'equal to the session model'));
|
|
101
|
+
ok('B: the protocol bullets above stay the agent\'s protocol', grilling.indexOf('Holistic review before the final confirmation') < at);
|
|
102
|
+
|
|
103
|
+
ok('B: grilling.md states that trailhead-grill output is always the report',
|
|
104
|
+
has('When it runs as `trailhead-grill`, its output is always the report', 'never asks the user live and never shows a menu'));
|
|
105
|
+
const agent = fs.readFileSync(path.join(root, 'plugins', 'trailhead', 'agents', 'trailhead-grill.md'), 'utf8');
|
|
106
|
+
ok('B: the agent follows the round bullets and its return contract, not the orchestrator section',
|
|
107
|
+
agent.includes('round bullets') && agent.includes('Return contract') && !agent.includes('Who runs a round'));
|
|
108
|
+
|
|
109
|
+
const dispatch = techniques.split('\n')[4] || '';
|
|
110
|
+
ok('B: techniques.md dispatch list maps Grilling to trailhead-grill', /Grilling → `trailhead-grill`/.test(dispatch));
|
|
111
|
+
ok('B: techniques.md read-only list includes trailhead-grill', /\*\*read-only\*\*[^)]*`trailhead-grill`/.test(dispatch));
|
|
112
|
+
const row = techniques.split('\n').find((l) => l.startsWith('| **Grilling** |')) || '';
|
|
113
|
+
ok('B: the Grilling index row names models.grill', row.includes('`models.grill`'));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- Parte C: i punti d'ingresso puntano a "Who runs a round" per nome -----------------
|
|
117
|
+
{
|
|
118
|
+
const POINTER = 'Who runs a round';
|
|
119
|
+
const lineWith = (text, needle) => text.split('\n').find((l) => l.includes(needle)) || '';
|
|
120
|
+
const points = (label, line) => ok(`C: ${label} points at ${POINTER} with the imperative routing wording`, line.length > 0 && line.includes(POINTER) && line.includes('grilling.md') && line.includes('first run the routing step at the top of'));
|
|
121
|
+
const chart = read('trailhead-chart/SKILL.md');
|
|
122
|
+
const standalone = chart.slice(chart.indexOf('## Standalone grill'));
|
|
123
|
+
points('chart SKILL.md Standalone grill', lineWith(standalone, 'Run a standalone **Grilling**'));
|
|
124
|
+
const charting = read('trailhead-chart/references/charting.md');
|
|
125
|
+
points('charting.md Mode 1 Name the destination', lineWith(charting, '1. **Name the destination.**'));
|
|
126
|
+
points('charting.md Mode 1 Map the frontier', lineWith(charting, '2. **Map the frontier.**'));
|
|
127
|
+
points('charting.md Mode 1-bis Map the frontier of the remainder', lineWith(charting, '4. **Map the frontier of the remainder**'));
|
|
128
|
+
points('charting.md Mode 1-bis Name the destination of the remaining stretch', lineWith(charting, '2. **Name the destination of the remaining stretch.**'));
|
|
129
|
+
const engines = read('trailhead-work/references/ticket-engines.md');
|
|
130
|
+
points('ticket-engines.md build Discuss', lineWith(engines, '1. **Discuss**: **never auto-grill.**'));
|
|
131
|
+
points('ticket-engines.md Plan needs_input grill option', lineWith(engines, 'needs_input` -> ask the user the `questions:`'));
|
|
132
|
+
ok('C: the Plan needs_input pointer is about opting to grill the planner questions',
|
|
133
|
+
/opts? to grill/.test(lineWith(engines, 'needs_input` -> ask the user the `questions:`')));
|
|
134
|
+
points('ticket-engines.md decision Converge', lineWith(engines, '2. **Converge.**'));
|
|
135
|
+
const work = read('trailhead-work/SKILL.md');
|
|
136
|
+
points('work SKILL.md quick Grill only if needed', lineWith(work, '- **Grill only if needed** (Discuss)'));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- Parte D: README e README.it.md ---------------------------------------------------
|
|
140
|
+
{
|
|
141
|
+
for (const file of ['README.md', 'README.it.md']) {
|
|
142
|
+
const text = fs.readFileSync(path.join(root, file), 'utf8');
|
|
143
|
+
const lines = text.split('\n');
|
|
144
|
+
const row = (prefix) => lines.find((l) => l.startsWith(prefix)) || '';
|
|
145
|
+
ok(`D: ${file} models.{...} row lists grill`, /^\| `models\.\{[^}]*codebase-map,grill\}`/.test(row('| `models.{')));
|
|
146
|
+
ok(`D: ${file} models.codex.{...} row lists grill`, /^\| `models\.codex\.\{[^}]*codebase-map,grill\}`/.test(row('| `models.codex.{')));
|
|
147
|
+
ok(`D: ${file} Models bullets carry a grill bullet`, lines.some((l) => /^- `grill`/.test(l) && l.includes('trailhead-grill')));
|
|
148
|
+
ok(`D: ${file} no longer says grilling runs on the session model`,
|
|
149
|
+
!/charting, grilling/.test(text));
|
|
150
|
+
ok(`D: ${file} says grill follows plan when unset`, lines.some((l) => /^- `grill`/.test(l) && l.includes('`plan`') && /unset|non impostat/.test(l)));
|
|
151
|
+
ok(`D: ${file} carries the Codex note`, text.includes('models.codex.grill') && /models\.codex\.plan/.test(lines.find((l) => l.includes('models.codex.grill') && !l.startsWith('|')) || ''));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`grill-model-docs.test.js: ${passed} checks passed`);
|
|
@@ -37,8 +37,12 @@ for (const frag of [
|
|
|
37
37
|
'contradictions, gaps, choices that break each other or the destination',
|
|
38
38
|
'by question number',
|
|
39
39
|
'say so in one line and go straight to the confirmation',
|
|
40
|
-
'
|
|
41
|
-
'
|
|
40
|
+
'Every non-working combination carries its fix',
|
|
41
|
+
'say why in one line and name where it goes',
|
|
42
|
+
'Accepting a combination as a risk is never the proposed fix',
|
|
43
|
+
'apply, amend or keep',
|
|
44
|
+
'apply (the first option, recommended) applies every proposed amendment and fix',
|
|
45
|
+
'keep leaves the choices as they were',
|
|
42
46
|
'consciously accepted risk',
|
|
43
47
|
'reopens that branch',
|
|
44
48
|
'the review runs again on the new set',
|
|
@@ -60,7 +64,7 @@ const pointer = (name, text, startsWith) => {
|
|
|
60
64
|
const l = text.split('\n').find((x) => x.startsWith(startsWith));
|
|
61
65
|
ok(`${name} found`, !!l);
|
|
62
66
|
ok(`${name} references the anchor`, l.includes(ANCHOR));
|
|
63
|
-
ok(`${name} does not restate the rule`, !l.includes('accepted risk') && !l.includes('
|
|
67
|
+
ok(`${name} does not restate the rule`, !l.includes('accepted risk') && !l.includes('keep leaves'));
|
|
64
68
|
};
|
|
65
69
|
pointer('chart SKILL standalone grill', read('trailhead-chart/SKILL.md'), 'Run a standalone **Grilling**');
|
|
66
70
|
const engines = read('trailhead-work/references/ticket-engines.md');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #352: code review has a fifth axis, Impossible-state guards,
|
|
3
|
+
// with an evidence rule, a Warning severity cap and explicit non-findings, and
|
|
4
|
+
// the reviewing agent's brief reaches it.
|
|
5
|
+
// Run: node guard-axis-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.join(__dirname, '..');
|
|
11
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
12
|
+
const agents = path.join(root, 'plugins', 'trailhead', 'agents');
|
|
13
|
+
const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
|
|
14
|
+
|
|
15
|
+
let passed = 0;
|
|
16
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
17
|
+
|
|
18
|
+
const review = read(skills, '_shared', 'techniques', 'code-review.md');
|
|
19
|
+
const techniques = read(skills, '_shared', 'techniques.md');
|
|
20
|
+
const engines = read(skills, 'trailhead-work', 'references', 'ticket-engines.md');
|
|
21
|
+
const agent = read(agents, 'trailhead-code-review.md');
|
|
22
|
+
|
|
23
|
+
const axis = (review.split('\n').find((l) => l.startsWith('- **Impossible-state guards**')) || '');
|
|
24
|
+
const classify = (review.split('\n').find((l) => l.startsWith('Classify each finding')) || '');
|
|
25
|
+
const reuse = (review.split('\n').find((l) => l.startsWith('- **Reuse & simplification**')) || '');
|
|
26
|
+
|
|
27
|
+
ok('code-review.md: review on five axes', review.includes('Review on five axes:'));
|
|
28
|
+
ok('code-review.md: no four-axes leftover', !review.includes('Review on four axes'));
|
|
29
|
+
|
|
30
|
+
ok('axis bullet exists', axis.length > 0);
|
|
31
|
+
for (const sub of ['a guard on an impossible state', 'defence already enforced elsewhere', 'a pattern copied without its premises', 'a stale or phantom requirement', 'an abstraction with one instance']) {
|
|
32
|
+
ok(`axis names subtype: ${sub}`, axis.includes(sub));
|
|
33
|
+
}
|
|
34
|
+
ok('axis extends the speculative generality smell', axis.includes('extends the speculative generality smell'));
|
|
35
|
+
|
|
36
|
+
ok('axis: the guard code is reachable, the state it guards must be shown impossible',
|
|
37
|
+
axis.includes("The guard's own code must be reachable (live), while the state it guards against must be shown impossible"));
|
|
38
|
+
ok('classify: this axis is the exception, what is verified is the impossibility',
|
|
39
|
+
classify.includes('except for Impossible-state guards, where what you verify is that the state cannot occur, not a reachable failing path'));
|
|
40
|
+
|
|
41
|
+
ok('axis: every finding cites concrete evidence the state cannot occur',
|
|
42
|
+
axis.includes('Every finding cites concrete evidence (file:line or a doc) that the state cannot occur'));
|
|
43
|
+
ok('axis: admissible evidence kinds',
|
|
44
|
+
axis.includes('a closed type or enum, an API or framework invariant, fixed configuration, validation already done upstream, or a guarantee of the stack itself'));
|
|
45
|
+
ok('axis: no evidence, no finding, never the reviewer\'s say-so',
|
|
46
|
+
axis.includes("Without such evidence there is no finding, never the reviewer's say-so"));
|
|
47
|
+
|
|
48
|
+
ok('axis: severity cap', axis.includes('at most Warning, never Critical, so it never enters the Critical-only fix-and-re-review loop'));
|
|
49
|
+
ok('classify: severity cap', classify.includes('Impossible-state guards findings are at most Warning, never Critical, so they never enter the Critical-only fix-and-re-review loop'));
|
|
50
|
+
|
|
51
|
+
ok('axis: explicit non-findings',
|
|
52
|
+
axis.includes('Not findings: trust boundaries (user input, network, files, env), deliberate defence-in-depth, test and DI seams, and dead code (a separate smell, not this axis)'));
|
|
53
|
+
ok('axis: exclusions win over the evidence',
|
|
54
|
+
axis.includes('The exclusions win over the evidence: no evidence, upstream validation included, turns a check at a trust boundary into a finding.'));
|
|
55
|
+
|
|
56
|
+
ok('reuse smell list points to the new axis', /speculative generality \(delete; see \*\*Impossible-state guards\*\*\)/.test(reuse));
|
|
57
|
+
|
|
58
|
+
ok('techniques.md: protocol carried in the spawn prompt', techniques.includes('carries its full protocol in the prompt'));
|
|
59
|
+
ok('techniques.md: Code review row on 5 axes', techniques.includes("review the ticket's own commits (its `Refs: #<n>` set) on 5 axes"));
|
|
60
|
+
const verifyLines = engines.split('\n').filter((l) => l.startsWith('4. **Verify**'));
|
|
61
|
+
ok('ticket-engines.md: build and bug Verify found', verifyLines.length === 2);
|
|
62
|
+
ok('ticket-engines.md: both Verify lines reference techniques/code-review.md', verifyLines.every((l) => l.includes('techniques/code-review.md')));
|
|
63
|
+
ok('agent: names the injected single source', agent.includes('injected at spawn from the trailhead skill\'s single source (`_shared/techniques/code-review.md`)'));
|
|
64
|
+
|
|
65
|
+
for (const [name, text] of Object.entries({ 'code-review.md': review, 'techniques.md': techniques, 'guard-axis-docs.test.js': read(__dirname, 'guard-axis-docs.test.js') })) {
|
|
66
|
+
ok(`${name} has no em-dash`, !text.includes(String.fromCharCode(0x2014)));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`guard-axis-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #354: an inbox issue waiting on its reporter is parked with
|
|
3
|
+
// `trailhead:needs-info`, resurfaces only on the reporter's answer, and is offered
|
|
4
|
+
// for closing (never auto-closed) after inbox.needs_info_days.
|
|
5
|
+
// Plain asserts, no framework. Run: node bin/inbox-needs-info-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 plugin = path.join(root, 'plugins', 'trailhead');
|
|
12
|
+
const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
|
|
13
|
+
let passed = 0;
|
|
14
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
15
|
+
|
|
16
|
+
const inbox = read('skills/trailhead-chart/references/inbox.md');
|
|
17
|
+
const commands = read('skills/_shared/substrate-commands.md');
|
|
18
|
+
const substrate = read('skills/_shared/substrate.md');
|
|
19
|
+
const reference = read('skills/_shared/configuration-reference.md');
|
|
20
|
+
const lineOf = (text, re) => (text.split('\n').find((l) => re.test(l)) || '');
|
|
21
|
+
|
|
22
|
+
// Four sections, by descriptive name.
|
|
23
|
+
ok('inbox.md presents four sections', inbox.includes('presents **four sections**') && !inbox.includes('presents **three sections**'));
|
|
24
|
+
ok('inbox.md has the Waiting on the reporter section', inbox.includes('**Waiting on the reporter.**'));
|
|
25
|
+
ok('the waiting section reads through the helper with the templates lookup', /trailhead-inbox-needs-info\.js --repo <owner\/name> --config-root <project root>/.test(inbox) && inbox.includes('same three-dir lookup as `trailhead-work-state.js`'));
|
|
26
|
+
ok('a failed helper never guesses answered or timed out', /never guess which are answered or timed out/.test(inbox));
|
|
27
|
+
ok('the proposal list covers the waiting section', /^For each issue in the first three sections \(incoming issues, waiting on the reporter, parked fog/m.test(inbox));
|
|
28
|
+
|
|
29
|
+
// The Needs info move.
|
|
30
|
+
const needsInfo = inbox.slice(inbox.indexOf('- **Needs info.**'));
|
|
31
|
+
ok('needs info: question posted first, then the label', /post the question \*\*first\*\*, then add `trailhead:needs-info`/.test(needsInfo));
|
|
32
|
+
ok('needs info: ensures the label idempotently', needsInfo.includes('gh label create trailhead:needs-info --color FBCA04') && needsInfo.includes('2>/dev/null || true'));
|
|
33
|
+
ok('resurfacing only on a reporter comment after the label', /only when the reporter comments after the label/.test(needsInfo));
|
|
34
|
+
ok('every move on an answered issue drops the label first', /dropping `trailhead:needs-info` first/.test(needsInfo));
|
|
35
|
+
ok('Ask again removes and re-adds the label', /\*\*Ask again\*\*[^\n]*removes and re-adds the label/.test(needsInfo));
|
|
36
|
+
ok('Keep waiting removes and re-adds the label, stating what a failure leaves', /\*\*Keep waiting\*\* removes and re-adds the label/.test(needsInfo) && /A failed removal keeps the old clock, so the offer returns next run; a failed re-add leaves the issue in the incoming queue/.test(needsInfo));
|
|
37
|
+
ok('a timeout is only an offer, never an automatic close', /only \*\*offered\*\* for closing/.test(needsInfo) && /never closed automatically/.test(needsInfo));
|
|
38
|
+
ok('the label comes off before the close', /remove the label, \*\*then\*\* `gh issue close <n> --reason "not planned"/.test(needsInfo));
|
|
39
|
+
ok('the close message follows ticket.language and asks for a new issue', needsInfo.includes('`config.ticket.language`') && needsInfo.includes('asks for a new issue linking this one'));
|
|
40
|
+
ok('failed steps are reported with the state left behind', /Any failed step of a move is reported naming the issue and the state it was left in/.test(needsInfo));
|
|
41
|
+
ok('the direct /trailhead:inbox <n> path triages whatever the state', /`\/trailhead:inbox <n>` on a needs-info issue triages it directly, whatever its state/.test(needsInfo));
|
|
42
|
+
ok('inbox.md has no em-dash', !inbox.includes(String.fromCharCode(8212)));
|
|
43
|
+
|
|
44
|
+
// The label set.
|
|
45
|
+
const firstUse = lineOf(commands, /^1\. Create any missing labels/);
|
|
46
|
+
ok('first-use setup lists the label among twenty-two', firstUse.includes('all twenty-two:') && firstUse.includes('`trailhead:needs-info`'));
|
|
47
|
+
ok('substrate-commands.md has the idempotent ensure line', commands.includes('gh label create trailhead:needs-info --color FBCA04 --description "Waiting on the reporter: back in the inbox when they answer" 2>/dev/null || true'));
|
|
48
|
+
ok('substrate.md documents the state label and the count', substrate.includes('**`trailhead:needs-info`**') && substrate.includes('twenty-two labels'));
|
|
49
|
+
ok('the labels guard exempts it (not a ticket)', /EXEMPT = \[[^\]]*'trailhead:needs-info'/.test(fs.readFileSync(path.join(plugin, 'hooks', 'trailhead-labels-guard.js'), 'utf8')));
|
|
50
|
+
ok('the label-guard Action strips any trailhead:* by prefix, so it needs no change', /label\.startsWith\('trailhead:'\)/.test(fs.readFileSync(path.join(root, '.github', 'workflows', 'trailhead-label-guard.yml'), 'utf8')));
|
|
51
|
+
|
|
52
|
+
// Config.
|
|
53
|
+
ok('configuration-reference documents inbox.needs_info_days, default 14', /\| `inbox\.needs_info_days` \| positive integer \(\*\*`14`\*\*\)/.test(reference) && reference.includes('**inbox.needs_info_days.**'));
|
|
54
|
+
|
|
55
|
+
// README mirror and site pages.
|
|
56
|
+
for (const f of ['README.md', 'README.it.md', 'site/src/content/docs/docs/configuration.mdx']) {
|
|
57
|
+
ok(`${f} documents inbox.needs_info_days`, fs.readFileSync(path.join(root, f), 'utf8').includes('`inbox.needs_info_days`'));
|
|
58
|
+
}
|
|
59
|
+
for (const f of ['README.md', 'README.it.md', 'site/src/content/docs/docs/concepts.md']) {
|
|
60
|
+
ok(`${f} lists the needs-info state label`, fs.readFileSync(path.join(root, f), 'utf8').includes('`needs-info` ('));
|
|
61
|
+
}
|
|
62
|
+
ok('chart.md shows four sections', fs.readFileSync(path.join(root, 'site/src/content/docs/docs/commands/chart.md'), 'utf8').includes('Shows four sections by name'));
|
|
63
|
+
const chartPage = fs.readFileSync(path.join(root, 'site/src/content/docs/docs/commands/chart.md'), 'utf8');
|
|
64
|
+
ok('chart.md no longer says the inbox decides alone', !chartPage.includes('alone on clear ones'));
|
|
65
|
+
ok('chart.md states the explicit yes', chartPage.includes('Nothing is written without your explicit yes'));
|
|
66
|
+
ok('this test has no em-dash', !fs.readFileSync(__filename, 'utf8').includes(String.fromCharCode(8212)));
|
|
67
|
+
|
|
68
|
+
console.log(`inbox-needs-info-docs.test.js: ${passed} checks passed`);
|