@marcomigozzi/trailhead 0.14.0 → 0.15.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 +118 -30
- package/README.md +107 -29
- package/bin/base-preflight-docs.test.js +65 -0
- package/bin/execute-split-docs.test.js +146 -0
- package/bin/isolation-answer-docs.test.js +41 -0
- package/bin/isolation-copy-docs.test.js +28 -0
- package/bin/lib/codex-projection.js +1 -1
- package/bin/lib/codex-projection.test.js +41 -0
- package/bin/report-status-docs.test.js +195 -0
- package/bin/resolve-helpers-docs.test.js +36 -0
- package/bin/scope-confirm-skip-docs.test.js +70 -0
- package/bin/trailhead.js +3 -3
- package/bin/trailhead.test.js +341 -1
- package/bin/uat-setup-docs.test.js +65 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +15 -2
- package/plugins/trailhead/agents/trailhead-codebase-map.md +20 -1
- package/plugins/trailhead/agents/trailhead-debug.md +20 -1
- package/plugins/trailhead/agents/trailhead-executor.md +29 -2
- package/plugins/trailhead/agents/trailhead-fix.md +29 -2
- package/plugins/trailhead/agents/trailhead-plan.md +20 -1
- package/plugins/trailhead/agents/trailhead-research.md +22 -3
- package/plugins/trailhead/agents/trailhead-verify.md +15 -2
- package/plugins/trailhead/hooks/lib/base-preflight.test.js +513 -0
- package/plugins/trailhead/hooks/lib/commit-message-check.js +1 -1
- package/plugins/trailhead/hooks/lib/commit-message-check.test.js +3 -2
- package/plugins/trailhead/hooks/lib/commit-msg-module-repo.test.js +116 -0
- package/plugins/trailhead/hooks/lib/commit-msg-sync.test.js +100 -9
- package/plugins/trailhead/hooks/lib/dashboard.test.js +409 -0
- package/plugins/trailhead/hooks/lib/fixtures/commit-msg-legacy-js +104 -0
- package/plugins/trailhead/hooks/lib/isolation-answer.test.js +258 -0
- package/plugins/trailhead/hooks/lib/isolation-copy.test.js +298 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +470 -0
- package/plugins/trailhead/hooks/lib/report-status.test.js +366 -0
- package/plugins/trailhead/hooks/lib/shell-scan.js +36 -1
- package/plugins/trailhead/hooks/lib/shell-scan.test.js +14 -0
- package/plugins/trailhead/hooks/lib/uat-backfill.test.js +150 -0
- package/plugins/trailhead/hooks/lib/uat-setup.test.js +340 -0
- package/plugins/trailhead/hooks/lib/work-state.test.js +45 -0
- package/plugins/trailhead/hooks/mod/README.md +38 -6
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +168 -15
- package/plugins/trailhead/hooks/mod/map-pane.tsx +275 -106
- package/plugins/trailhead/hooks/mod/register.tsx +15 -2
- package/plugins/trailhead/hooks/mod/work-state-view.ts +17 -0
- package/plugins/trailhead/hooks/trailhead-install-guard.js +169 -34
- package/plugins/trailhead/hooks/trailhead-install-guard.test.js +180 -1
- package/plugins/trailhead/hooks/trailhead-search-guard.js +43 -7
- package/plugins/trailhead/hooks/trailhead-search-guard.test.js +66 -2
- package/plugins/trailhead/skills/_shared/choices.md +4 -2
- package/plugins/trailhead/skills/_shared/configuration-reference.md +15 -3
- package/plugins/trailhead/skills/_shared/load-first.md +3 -3
- package/plugins/trailhead/skills/_shared/multi-map.md +2 -2
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +22 -11
- package/plugins/trailhead/skills/_shared/substrate.md +6 -4
- package/plugins/trailhead/skills/_shared/teamwork.md +16 -7
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +17 -9
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/debug.md +2 -0
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/verify.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques.md +12 -2
- package/plugins/trailhead/skills/trailhead/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +2 -2
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +1 -1
- package/plugins/trailhead/skills/trailhead-manage/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +1 -1
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +8 -6
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +14 -12
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +4 -4
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +29 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +26 -10
- package/plugins/trailhead/templates/trailhead-base-preflight.js +305 -0
- package/plugins/trailhead/templates/trailhead-commit-msg +38 -102
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +58 -27
- package/plugins/trailhead/templates/trailhead-commit-msg.cjs +114 -0
- package/plugins/trailhead/templates/trailhead-dashboard.js +340 -0
- package/plugins/trailhead/templates/trailhead-isolation-answer.js +227 -0
- package/plugins/trailhead/templates/trailhead-isolation-copy.js +239 -0
- package/plugins/trailhead/templates/trailhead-issue-body.js +459 -0
- package/plugins/trailhead/templates/trailhead-report-status.js +274 -0
- package/plugins/trailhead/templates/trailhead-uat-backfill.js +114 -0
- package/plugins/trailhead/templates/trailhead-uat-setup.js +385 -0
- package/plugins/trailhead/templates/trailhead-work-state.js +25 -7
- package/plugins/trailhead/tests/map-pane-ui.test.ts +566 -19
- package/plugins/trailhead/tests/map-pane-view.test.ts +304 -16
- package/plugins/trailhead/tests/work-state-view.test.ts +25 -1
- package/plugins/trailhead/types/index.d.ts +37 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #242: the skill prose names the gitignored-file copy helper for new
|
|
3
|
+
// isolation workspaces and documents the isolation.copy config key.
|
|
4
|
+
// Run: node isolation-copy-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
|
+
// file -> strings it must carry
|
|
16
|
+
const EXPECT = {
|
|
17
|
+
'trailhead-work/SKILL.md': ['trailhead-isolation-copy.js --from <main-checkout> --to <working-root>', 'Gitignored files in a new workspace'],
|
|
18
|
+
'_shared/teamwork.md': ['trailhead-isolation-copy.js --from <main-checkout> --to <working-root>', 'Gitignored files in a new workspace', 'isolation.copy', 'before any install', '`rejected`', '`warnings`', '`status: skipped`'],
|
|
19
|
+
'_shared/configuration-reference.md': ['`isolation.copy`', 'Isolation copy', '.example', '.sample'],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
for (const [rel, needles] of Object.entries(EXPECT)) {
|
|
23
|
+
const text = read(rel);
|
|
24
|
+
for (const needle of needles) ok(`${rel} names: ${needle}`, text.includes(needle));
|
|
25
|
+
ok(`${rel} has no em-dash`, !text.includes('—'));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`isolation-copy-docs.test.js: ${passed} checks passed`);
|
|
@@ -91,7 +91,7 @@ This trailhead engine is running on **Codex CLI**, not Claude Code. The installe
|
|
|
91
91
|
trailhead is a native Codex skill. Invoke it as \`$trailhead <verb>\` (e.g. \`$trailhead work\`, \`$trailhead new "idea"\`); bare \`$trailhead\` is smart entry. Never \`/trailhead:<verb>\` (that is the Claude Code surface). Whatever you type after the verb is the arguments. For discoverability the installer also projects one thin skill per verb (\`$trailhead-work\`, \`$trailhead-bug\`, …) that just delegates to \`$trailhead <verb>\`, so the verbs surface in the \`$\`-menu; \`$trailhead <verb>\` stays the canonical form and this SKILL.md the single source of truth. (Codex custom prompts under \`~/.codex/prompts/\` are deliberately NOT used: they do not surface as slash commands.)
|
|
92
92
|
|
|
93
93
|
## B. This IS the skill; the engine is split across sibling skill dirs
|
|
94
|
-
Codex loaded this SKILL.md because you invoked \`$trailhead\` (or a \`$trailhead-<verb>\` / \`$trailhead-<cluster>\` entry); there is no separate Skill tool to call. The engine is split across sibling skill dirs under \`~/.codex/skills/\`: the dispatcher (\`trailhead/\`), five cluster skills (\`trailhead-chart/\`, \`trailhead-work/\`, \`trailhead-view/\`, \`trailhead-capture/\`, \`trailhead-manage/\`), and the shared core (\`_shared/\`, no SKILL.md).
|
|
94
|
+
Codex loaded this SKILL.md because you invoked \`$trailhead\` (or a \`$trailhead-<verb>\` / \`$trailhead-<cluster>\` entry); there is no separate Skill tool to call. The engine is split across sibling skill dirs under \`~/.codex/skills/\`: the dispatcher (\`trailhead/\`), five cluster skills (\`trailhead-chart/\`, \`trailhead-work/\`, \`trailhead-view/\`, \`trailhead-capture/\`, \`trailhead-manage/\`), and the shared core (\`_shared/\`, no SKILL.md). Resolve every referenced file **relative to the SKILL.md you are currently in** into an absolute path, then read it with a single \`cat <absolute path>\` or the file-read tool (never \`cd\` into a skills dir and then read a relative path): a \`references/*.md\` from this skill's own dir, and the shared core (\`../_shared/*.md\`, or \`../../_shared/*.md\` from a \`references/\` file) from the sibling \`_shared/\` dir. Where the dispatcher's routing says "call the Skill tool with skill name \`trailhead-<cluster>\` and arguments \`<verb> <rest>\`", there is no Skill tool on Codex: **Read \`../trailhead-<cluster>/SKILL.md\` (the sibling dir) and follow it**, treating \`<verb> <rest>\` as its arguments.
|
|
95
95
|
|
|
96
96
|
## C. AskUserQuestion -> plain-text numbered list (the guaranteed path on Codex)
|
|
97
97
|
Codex's \`request_user_input\` tool only works in **Plan Mode** (Codex's collaboration mode with a plan-review step). trailhead skills run in Codex's **Default mode**: the installer never enables \`collaboration_modes\` / Plan Mode, so \`request_user_input\` is unavailable here. This is a **permanent, known constraint** of running trailhead on Codex, not a conditional "if unavailable" case.
|
|
@@ -410,6 +410,11 @@ ok('convertToCodex projects the anchor positives to Codex forms', (() => {
|
|
|
410
410
|
// the projected work skill and handoff carry the set/clear calls.
|
|
411
411
|
ok('convertToCodex keeps the trailhead-work-state.js filename',
|
|
412
412
|
convertToCodex('node <templates>/trailhead-work-state.js set').includes('<templates>/trailhead-work-state.js set'));
|
|
413
|
+
// #253: the Resolve bookkeeping helper filenames survive the projection.
|
|
414
|
+
ok('convertToCodex keeps the trailhead-issue-body.js filename',
|
|
415
|
+
convertToCodex('node <templates>/trailhead-issue-body.js append --issue 1').includes('<templates>/trailhead-issue-body.js append'));
|
|
416
|
+
ok('convertToCodex keeps the trailhead-dashboard.js filename',
|
|
417
|
+
convertToCodex('node <templates>/trailhead-dashboard.js --dry-run').includes('<templates>/trailhead-dashboard.js --dry-run'));
|
|
413
418
|
ok('convertToCodex keeps CLAUDE_CODE_SESSION_ID intact',
|
|
414
419
|
convertToCodex('session from CLAUDE_CODE_SESSION_ID when set').includes('CLAUDE_CODE_SESSION_ID'));
|
|
415
420
|
{
|
|
@@ -417,6 +422,12 @@ ok('convertToCodex keeps CLAUDE_CODE_SESSION_ID intact',
|
|
|
417
422
|
const workSkill = convertToCodex(fs.readFileSync(path.join(skillsDir, 'trailhead-work', 'SKILL.md'), 'utf8'));
|
|
418
423
|
ok('projected work skill carries the work-start set call',
|
|
419
424
|
workSkill.includes('trailhead-work-state.js set --repo <working-root> --map <n> --ticket <n> --title "<title>" --step discuss'));
|
|
425
|
+
// #234: the scope confirm menu call-site and the adapter's numbered-list rule both reach Codex.
|
|
426
|
+
const workInjected = injectCodexAdapterHeader(workSkill);
|
|
427
|
+
ok('projected work skill carries the step 3 scope-confirm menu call-site',
|
|
428
|
+
/AskUserQuestion[\s\S]{0,300}\*\*Proceed\*\*/.test(workInjected));
|
|
429
|
+
ok('projected work skill carries the adapter AskUserQuestion -> numbered list rule',
|
|
430
|
+
workInjected.includes('## C. AskUserQuestion -> plain-text numbered list'));
|
|
420
431
|
const handoff = convertToCodex(fs.readFileSync(path.join(skillsDir, '_shared', 'session-handoff.md'), 'utf8'));
|
|
421
432
|
ok('projected session handoff carries the work state clear call',
|
|
422
433
|
handoff.includes('trailhead-work-state.js clear --repo <working-root>'));
|
|
@@ -430,4 +441,34 @@ ok('convertToCodex keeps CLAUDE_CODE_SESSION_ID intact',
|
|
|
430
441
|
autoRef.includes('--auto true --auto-done <n>'));
|
|
431
442
|
}
|
|
432
443
|
|
|
444
|
+
// #248: the shared boundary blocks survive the Codex projection of every real agent.
|
|
445
|
+
{
|
|
446
|
+
const { parseSource } = require('../../scripts/sync-agent-blocks.js');
|
|
447
|
+
const shared = parseSource(fs.readFileSync(path.join(__dirname, '../../scripts/agent-blocks.md'), 'utf8'));
|
|
448
|
+
const agentsDir = path.join(__dirname, '../../plugins/trailhead/agents');
|
|
449
|
+
const files = fs.readdirSync(agentsDir).filter((f) => /^trailhead-.*\.md$/.test(f));
|
|
450
|
+
ok('projection boundaries: found the eight real agents', files.length === 8);
|
|
451
|
+
ok('projection boundaries: source has common and implementing blocks', !!shared.common && !!shared.implementing);
|
|
452
|
+
// #249: every agent's projected TOML carries exactly its family block, whole.
|
|
453
|
+
const familyOf = {
|
|
454
|
+
'trailhead-verify': 'reviewing', 'trailhead-code-review': 'reviewing',
|
|
455
|
+
'trailhead-executor': 'implementing', 'trailhead-fix': 'implementing',
|
|
456
|
+
'trailhead-debug': 'investigating', 'trailhead-research': 'investigating', 'trailhead-codebase-map': 'investigating',
|
|
457
|
+
'trailhead-plan': 'plan',
|
|
458
|
+
};
|
|
459
|
+
const families = ['reviewing', 'implementing', 'investigating', 'plan'];
|
|
460
|
+
ok('projection boundaries: source has every family block', families.every((f) => !!shared[f]));
|
|
461
|
+
for (const f of files) {
|
|
462
|
+
const name = f.slice(0, -3);
|
|
463
|
+
const body = fs.readFileSync(path.join(agentsDir, f), 'utf8').replace(/^---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/, '');
|
|
464
|
+
const out = codexAgentToml({ name, description: 'd', developerInstructions: body, model: null, effort: null });
|
|
465
|
+
ok(`${name}: projection carries the whole common block`, !!shared.common && out.includes(shared.common));
|
|
466
|
+
ok(`${name}: has a family`, families.includes(familyOf[name]));
|
|
467
|
+
for (const fam of families) {
|
|
468
|
+
ok(`${name}: projection ${familyOf[name] === fam ? 'carries the whole' : 'omits the'} ${fam} block`,
|
|
469
|
+
!!shared[fam] && out.includes(shared[fam]) === (familyOf[name] === fam));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
433
474
|
console.log(`✓ codex-projection: ${passed} assertions passed`);
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Pins the report status protocol in the shipped skills (#250, #249): the
|
|
3
|
+
// canonical "Malformed review report" paragraph (every engine-agent report) and
|
|
4
|
+
// VERIFY comment layout in _shared/techniques.md, the clause on both Verify lines of
|
|
5
|
+
// ticket-engines.md, the return-not-post wording in the verify and code-review
|
|
6
|
+
// techniques, and how Plan, Execute, Fix, Diagnose, research and the codebase map
|
|
7
|
+
// read the fields of their agent's report.
|
|
8
|
+
// Run: node report-status-docs.test.js. No framework: plain asserts.
|
|
9
|
+
const assert = require('assert');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
let passed = 0;
|
|
14
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
15
|
+
|
|
16
|
+
const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
|
|
17
|
+
const read = (...p) => fs.readFileSync(path.join(skills, ...p), 'utf8');
|
|
18
|
+
const techniques = read('_shared', 'techniques.md');
|
|
19
|
+
const verify = read('_shared', 'techniques', 'verify.md');
|
|
20
|
+
const codeReview = read('_shared', 'techniques', 'code-review.md');
|
|
21
|
+
const acceptance = read('_shared', 'techniques', 'acceptance-testing.md');
|
|
22
|
+
const engines = read('trailhead-work', 'references', 'ticket-engines.md');
|
|
23
|
+
const brief = read('trailhead-work', 'references', 'executor-brief.md');
|
|
24
|
+
const research = read('_shared', 'techniques', 'research.md');
|
|
25
|
+
const codebaseMap = read('_shared', 'techniques', 'codebase-map.md');
|
|
26
|
+
const debug = read('_shared', 'techniques', 'debug.md');
|
|
27
|
+
const charting = read('trailhead-chart', 'references', 'charting.md');
|
|
28
|
+
const researchAgent = fs.readFileSync(path.join(__dirname, '..', 'plugins', 'trailhead', 'agents', 'trailhead-research.md'), 'utf8');
|
|
29
|
+
|
|
30
|
+
// --- the canonical paragraph -------------------------------------------------------
|
|
31
|
+
const start = techniques.indexOf('**Malformed review report');
|
|
32
|
+
ok('techniques.md has the Malformed review report paragraph', start !== -1);
|
|
33
|
+
const neverBlock = techniques.indexOf('**Reviews never block the session');
|
|
34
|
+
ok('the paragraph sits next to "Reviews never block the session"',
|
|
35
|
+
start !== -1 && neverBlock !== -1 && Math.abs(start - neverBlock) < 2500 && start > neverBlock);
|
|
36
|
+
const para = start === -1 ? '' : techniques.slice(start, techniques.indexOf('\n| Technique |', start));
|
|
37
|
+
const lc = para.toLowerCase();
|
|
38
|
+
ok('the paragraph section was found and is bounded', para.length > 1000 && !para.includes('| **Grilling**'));
|
|
39
|
+
|
|
40
|
+
ok('it MUST run on every engine-agent report, the two reviews included', /MUST run on every/.test(para)
|
|
41
|
+
&& para.includes('Code review') && para.includes('Goal-backward verification'));
|
|
42
|
+
ok('it names every technique it covers',
|
|
43
|
+
['plan', 'executor', 'fix', 'debug', 'research', 'codebase-map'].every((t) => para.includes(t)) && lc.includes('every engine-agent report'));
|
|
44
|
+
ok('the checker line carries fields and body', para.includes('{"action","verdict","problem","report","fields","body"}'));
|
|
45
|
+
ok('it covers subagent, inline and the lean inline pass', lc.includes('subagent') && lc.includes('inline') && lc.includes('lean inline pass'));
|
|
46
|
+
ok('the checker command with its technique flag and a report file',
|
|
47
|
+
para.includes('node <templates>/trailhead-report-status.js --technique <verify|code-review|plan|executor|fix|debug|research|codebase-map> --file <that dir>/report.md'));
|
|
48
|
+
ok('the three templates dirs, in order',
|
|
49
|
+
(() => {
|
|
50
|
+
const a = para.indexOf('${CLAUDE_PLUGIN_ROOT}/templates/');
|
|
51
|
+
const b = para.indexOf('~/.claude/trailhead/templates/');
|
|
52
|
+
const c = para.indexOf('~/.codex/skills/trailhead/templates/');
|
|
53
|
+
return a !== -1 && b > a && c > b;
|
|
54
|
+
})());
|
|
55
|
+
ok('the plugin root dir is skipped when unset', lc.includes('skipped when unset'));
|
|
56
|
+
ok('a private temp dir from mktemp -d, once per report', para.includes('`mktemp -d`') && lc.includes('once') && lc.includes('0700'));
|
|
57
|
+
ok('the raw report is written to report.md at the literal printed path with the Write tool',
|
|
58
|
+
para.includes('<that dir>/report.md') && para.includes('Write tool') && lc.includes('literal printed path'));
|
|
59
|
+
ok('the dir is removed with rm -rf at the literal absolute path', para.includes('rm -rf <that dir>') && lc.includes('literal absolute path'));
|
|
60
|
+
ok('cleanup runs after every path',
|
|
61
|
+
lc.includes('cleanup runs after every path') && ['accept', 'normalise', 're-ask check', 'second failure', 'checker failure'].every((w) => lc.includes(w)));
|
|
62
|
+
ok('never a fixed or predictable path, never reused', lc.includes('never a fixed or predictable path') && lc.includes('never reused'));
|
|
63
|
+
ok('stdin stays a CLI input but the protocol does not use it', lc.includes('stdin') && lc.includes('does not use'));
|
|
64
|
+
ok('the checker-returned report replaces the raw report', lc.includes('checker-returned `report` replaces the raw report'));
|
|
65
|
+
ok('the engine branches only on the returned verdict', para.includes('branches only on the returned `verdict`'));
|
|
66
|
+
ok('the engine reads the returned fields and body, never re-derives them from prose',
|
|
67
|
+
para.includes('reads the returned `fields`') && para.includes('`body`') && lc.includes('instead of re-deriving them from the prose'));
|
|
68
|
+
ok('reask: ask once, continue the same agent where the host allows, else re-dispatch',
|
|
69
|
+
lc.includes('ask once') && lc.includes('continue the same agent') && lc.includes('re-dispatch the same `trailhead-<technique>` agent')
|
|
70
|
+
&& lc.includes('same model override') && lc.includes('malformed report quoted'));
|
|
71
|
+
const prompt = 'Your report could not be accepted: `<problem>` (missing: no `status:` line; unknown-token: `<found>` is not an allowed token; '
|
|
72
|
+
+ 'conflicting: more than one `status:` line, so quote or reword any status text in the body, which must not start a line with `status:` outside a code fence or blockquote; counts: the counts line is missing or repeated; fields: a required field is missing or an invariant is broken). '
|
|
73
|
+
+ 'Return the complete report again with its first line exactly `status: <token>`, where `<token>` is one of `<allowed tokens>`, '
|
|
74
|
+
+ 'and no other `status:` line<for code-review: , and its second line exactly `critical: <n> · warning: <n> · info: <n>`>'
|
|
75
|
+
+ '<for fields: , with each of `<required fields>` as a top-level `<label>:` line and `<the broken invariant>` holding, with nothing after the last field>. '
|
|
76
|
+
+ 'Do not post it anywhere.';
|
|
77
|
+
ok('the corrective prompt, verbatim', para.includes(prompt));
|
|
78
|
+
ok('for code-review the prompt always restates both required lines', lc.includes('always restates both required lines'));
|
|
79
|
+
ok('for fields the prompt names the missing fields and the broken invariant', lc.includes('names the missing or empty fields and the invariant that broke'));
|
|
80
|
+
ok('the reply is re-checked', lc.includes('re-check'));
|
|
81
|
+
ok('a second failure: no verdict, nothing posted, no third ask, no inference from the body',
|
|
82
|
+
lc.includes('no verdict') && lc.includes('nothing posted') && lc.includes('no third ask') && lc.includes('no inference from the body'));
|
|
83
|
+
ok('a second failure: HITL checkpoint naming the technique and the problem, Resolve stays blocked',
|
|
84
|
+
para.includes('HITL checkpoint') && lc.includes('naming the technique and the problem') && lc.includes('resolve stays blocked'));
|
|
85
|
+
ok('the previous VERIFY comment stays latest', lc.includes('previous `verify` comment stays latest'));
|
|
86
|
+
ok('a step that needed the report stays blocked too', lc.includes('so does any step that needed the report'));
|
|
87
|
+
ok('the layout part is for the reviewing pair only', para.includes('**VERIFY comment layout (the reviewing pair only).**'));
|
|
88
|
+
ok('checker missing, exit 2 or non-JSON: apply the same rules by hand, never guess',
|
|
89
|
+
lc.includes('checker is missing') && lc.includes('exit 2') && lc.includes('non-json') && lc.includes('by hand') && lc.includes('never guess a verdict'));
|
|
90
|
+
|
|
91
|
+
// --- the VERIFY comment layout ------------------------------------------------------
|
|
92
|
+
ok('layout: one VERIFY comment per Verify round', /one `VERIFY` comment per Verify round/.test(para));
|
|
93
|
+
ok('layout part 1: the code-review report verbatim, status line 1, counts line 2, ledger, findings',
|
|
94
|
+
lc.includes('code-review report verbatim') && lc.includes('counts line 2') && lc.includes('ledger'));
|
|
95
|
+
ok('layout part 2: a "## Goal-backward verification" heading and the verify report verbatim',
|
|
96
|
+
para.includes('`## Goal-backward verification` heading') && lc.includes('verify report verbatim'));
|
|
97
|
+
ok('layout part 3: test or criterion outcome, then acceptance testing results',
|
|
98
|
+
lc.includes('test/criterion outcome') && lc.includes('acceptance-testing results'));
|
|
99
|
+
ok('layout: an issues_found round posts parts 1 and 3 only', lc.includes('`issues_found` posts parts 1 and 3 only'));
|
|
100
|
+
ok('layout: a verify-only round repeats the latest code-review report verbatim as part 1',
|
|
101
|
+
lc.includes('repeats the latest checker-returned code-review report verbatim as part 1'));
|
|
102
|
+
ok('layout: the latest VERIFY comment holds the counts line and ledger', lc.includes('latest `verify` comment always holds the authoritative counts line and ledger'));
|
|
103
|
+
ok('layout: the engine never reads the comment back to branch', lc.includes('never by reading the comment back'));
|
|
104
|
+
|
|
105
|
+
// --- both Verify lines -----------------------------------------------------------------
|
|
106
|
+
const verifyLines = engines.split('\n').filter((l) => l.trim().startsWith('4. **Verify**'));
|
|
107
|
+
ok('ticket-engines.md has exactly two Verify (step 4) lines', verifyLines.length === 2);
|
|
108
|
+
for (const [i, l] of verifyLines.entries()) {
|
|
109
|
+
ok(`Verify line ${i + 1}: run the report-status check before branching on status`, l.includes('run the report-status check before branching on status'));
|
|
110
|
+
ok(`Verify line ${i + 1}: points to the paragraph by name`, l.includes('*Malformed review report*') && l.includes('../../_shared/techniques.md'));
|
|
111
|
+
ok(`Verify line ${i + 1}: points to the VERIFY comment layout`, l.includes('layout'));
|
|
112
|
+
ok(`Verify line ${i + 1}: keeps the Ticket diff reference (#181)`, l.includes('**Ticket diff**'));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// --- the techniques return, the orchestrator posts --------------------------------------
|
|
116
|
+
ok('verify.md: "Return the verdict" with a leading status', verify.includes('Return the verdict with a leading `status:`'));
|
|
117
|
+
ok('verify.md: the orchestrator posts it under the Goal-backward verification heading of the round\'s VERIFY comment',
|
|
118
|
+
verify.includes('the orchestrator posts it') && verify.includes('`## Goal-backward verification` heading') && verify.includes("round's `VERIFY` comment"));
|
|
119
|
+
ok('verify.md: no "Post the result"', !verify.includes('Post the result'));
|
|
120
|
+
ok('verify.md keeps the three tokens', ['met', 'gaps_found', 'inconclusive'].every((t) => verify.includes(`\`status: ${t}\``)));
|
|
121
|
+
ok('code-review.md: returns the report and the orchestrator posts it as the VERIFY comment',
|
|
122
|
+
/Return the report/.test(codeReview) && codeReview.includes('the orchestrator posts it as the `VERIFY` comment'));
|
|
123
|
+
ok('code-review.md: no "Report in the ticket\'s VERIFY comment"', !codeReview.includes("Report in the ticket's `VERIFY` comment"));
|
|
124
|
+
ok('code-review.md: status tag still on its own first line', codeReview.includes('machine-checkable status tag on its own first line'));
|
|
125
|
+
ok('code-review.md: counts line phrasing kept', codeReview.includes('critical: <n> · warning: <n> · info: <n>')
|
|
126
|
+
&& codeReview.includes('**Counts line, right under `status:`.**'));
|
|
127
|
+
ok('code-review.md: dispositions and the latest VERIFY comment kept',
|
|
128
|
+
codeReview.includes('`fixed (<commit>)`') && codeReview.includes('latest `VERIFY` comment'));
|
|
129
|
+
ok('code-review.md keeps the #181 Ticket diff rule', codeReview.includes("**Ticket diff: review the ticket's own commits, never a range.**"));
|
|
130
|
+
|
|
131
|
+
// --- acceptance results are part 3 of the round's single VERIFY comment ---------------
|
|
132
|
+
ok('acceptance-testing.md: results are gathered as it goes and land in part 3 of the round\'s VERIFY comment',
|
|
133
|
+
acceptance.includes('as it goes') && acceptance.includes("part 3 of the round's single `VERIFY` comment"));
|
|
134
|
+
ok('acceptance-testing.md: the orchestrator posts once the round\'s checks, including acceptance, are done',
|
|
135
|
+
acceptance.includes('the orchestrator posts once the round\'s checks, including this one, are done'));
|
|
136
|
+
ok('acceptance-testing.md: no longer records "in the VERIFY comment as it goes"', !acceptance.includes('in the `VERIFY` comment as it goes'));
|
|
137
|
+
ok('acceptance-testing.md: the screenshot and the pending checklist go to part 3, not straight into a comment',
|
|
138
|
+
!acceptance.includes('screenshot into the `VERIFY` comment') && !acceptance.includes('checklist in the `VERIFY` comment'));
|
|
139
|
+
ok('acceptance-testing.md: the two start paths outside a Verify round post their own VERIFY comment',
|
|
140
|
+
acceptance.includes("the Pending-UAT guard's option (a) and a UAT follow-up, there is no round: the orchestrator posts the results as a `VERIFY` comment of their own"));
|
|
141
|
+
ok('acceptance-testing.md: no em-dash', !acceptance.includes('\u2014'));
|
|
142
|
+
|
|
143
|
+
// --- every engine step reads the fields of its agent's report (#249) --------------------
|
|
144
|
+
const stepOf = (src, startPrefix, endPrefix) => {
|
|
145
|
+
const ls = src.split('\n');
|
|
146
|
+
const a = ls.findIndex((l) => l.startsWith(startPrefix));
|
|
147
|
+
const b = ls.findIndex((l, i) => i > a && l.startsWith(endPrefix));
|
|
148
|
+
return a === -1 ? '' : ls.slice(a, b === -1 ? undefined : b).join('\n');
|
|
149
|
+
};
|
|
150
|
+
const has = (src, ...needles) => needles.every((n) => src.includes(n));
|
|
151
|
+
const plan = stepOf(engines, '2. **Plan**', '3. **Execute**');
|
|
152
|
+
const execute = stepOf(engines, '3. **Execute**', '4. **Verify**');
|
|
153
|
+
const fixStep = stepOf(engines, '3. **Fix**', '4. **Verify**');
|
|
154
|
+
const diagnose = stepOf(engines, '2. **Diagnose**', '3. **Fix**');
|
|
155
|
+
ok('the steps were found', [plan, execute, fixStep, diagnose].every((x) => x.length > 300));
|
|
156
|
+
ok('Plan: checks the planner report with --technique plan', has(plan, '--technique plan', '*Malformed review report*'));
|
|
157
|
+
ok('Plan: ready posts the checker body verbatim as the PLAN comment', has(plan, '`status: ready`', "checker's `body` verbatim", '`PLAN` comment'));
|
|
158
|
+
ok('Plan: needs_input asks the questions, re-spawns with the answers, never guesses',
|
|
159
|
+
has(plan, '`status: needs_input`', '`questions:`', 're-spawn the planner with the answers', 'never answer them yourself or guess'));
|
|
160
|
+
for (const [label, step, tech] of [['Execute', execute, 'executor'], ['Fix', fixStep, 'fix']]) {
|
|
161
|
+
ok(`${label}: checks the report with --technique ${tech}`, has(step, `--technique ${tech}`, '*Malformed review report*'));
|
|
162
|
+
ok(`${label}: branches on the verdict and reads the fields, never reconstructs from prose`,
|
|
163
|
+
has(step, "checker's `verdict`", '`fields`', 'never reconstruct a result from the prose'));
|
|
164
|
+
ok(`${label}: done goes to Verify`, has(step, '`status: done` -> Verify'));
|
|
165
|
+
ok(`${label}: partial or blocked acts on each blockers entry, then re-spawns`,
|
|
166
|
+
has(step, '`status: partial` or `blocked`', 'each `blockers:` entry', 'foreign change', 're-spawn'));
|
|
167
|
+
ok(`${label}: posts fidelity as the fidelity comment and routes surfaced scope`, has(step, 'Post `fidelity:` as the fidelity comment', '`surfaced scope:`'));
|
|
168
|
+
ok(`${label}: commits must equal the Refs set since the base, both directions, every SHA resolves, a mismatch is surfaced`,
|
|
169
|
+
has(step, '`commits:`', 'must equal the `Refs: #<n>` commit set since the base SHA', 'both directions', 'every SHA must resolve', 'surfaced to the user, never patched over'));
|
|
170
|
+
}
|
|
171
|
+
ok('Diagnose: checks the debug report with --technique debug', has(diagnose, '--technique debug', '*Malformed review report*'));
|
|
172
|
+
ok('Diagnose: found feeds the DIAGNOSIS from answer and evidence', has(diagnose, '`status: found`', '`answer:`', '`evidence:`', '`DIAGNOSIS`'));
|
|
173
|
+
ok('Diagnose: inconclusive stays in diagnosis, needs_input asks the open questions',
|
|
174
|
+
has(diagnose, '`status: inconclusive`', 'stays in diagnosis', '`status: needs_input`', '`open questions:`'));
|
|
175
|
+
ok('research: no research/<name> branch anywhere', ![research, charting, researchAgent].some((t) => /research\/(<name>|<name)/.test(t)));
|
|
176
|
+
ok('research: the report is the artifact, posted as a RESEARCH comment with every claim and its URL',
|
|
177
|
+
has(research, 'the report is the artifact', '`RESEARCH` comment', '`evidence:`', 'URL'));
|
|
178
|
+
ok('research: branches on found, inconclusive and needs_input',
|
|
179
|
+
has(research, '`status: found`', 'resolve the ticket', '`status: inconclusive`', 'accept the answer at that confidence, narrow the question, or run another round',
|
|
180
|
+
'`status: needs_input`', '`open questions:`', 're-spawn'));
|
|
181
|
+
ok('research: checked with --technique research', research.includes('--technique research'));
|
|
182
|
+
ok('charting: the research fan-out posts the report, no branch', has(charting, '`RESEARCH` comment', 'no branch'));
|
|
183
|
+
ok('research agent: identity returns findings in the report', researchAgent.includes('returning findings with their citations in your report') && researchAgent.includes('commit nothing'));
|
|
184
|
+
ok('codebase-map: each reader returns an investigating report checked with --technique codebase-map',
|
|
185
|
+
has(codebaseMap, 'investigating report', '--technique codebase-map'));
|
|
186
|
+
ok('codebase-map: distils the answer fields; inconclusive notes the gap; needs_input asked once then re-spawned or unmapped',
|
|
187
|
+
has(codebaseMap, 'distil** the `answer:` fields', '`status: inconclusive`', 'note the gap', '`status: needs_input`', 'once', 're-spawn that facet', 'unmapped'));
|
|
188
|
+
ok('debug: a Report line points at the investigating contract', has(debug, '**Report.**', 'investigating report', 'never commits or creates a branch', '`DIAGNOSIS`'));
|
|
189
|
+
ok('brief: the report names the implementing Return contract', has(brief, 'implementing `Return contract`', '`blockers:`'));
|
|
190
|
+
|
|
191
|
+
for (const [label, src] of [['techniques.md', techniques], ['verify.md', verify], ['code-review.md', codeReview], ['ticket-engines.md', engines], ['executor-brief.md', brief], ['research.md', research], ['codebase-map.md', codebaseMap], ['debug.md', debug], ['charting.md', charting]]) {
|
|
192
|
+
ok(`${label}: no em-dash`, !src.includes('—'));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(`report-status-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #253: the skill prose names the Resolve bookkeeping helpers as the
|
|
3
|
+
// default path (trailhead-issue-body.js, trailhead-dashboard.js, and the one `clear`
|
|
4
|
+
// that also removes the session-ticket marker), while keeping the by-hand fallback.
|
|
5
|
+
// Run: node resolve-helpers-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
|
|
11
|
+
const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
|
|
12
|
+
|
|
13
|
+
let passed = 0;
|
|
14
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
15
|
+
|
|
16
|
+
// file -> strings it must carry
|
|
17
|
+
const EXPECT = {
|
|
18
|
+
'_shared/substrate.md': ['trailhead-issue-body.js', 'trailhead-dashboard.js', 'when a helper is missing'],
|
|
19
|
+
'_shared/substrate-commands.md': ['trailhead-issue-body.js append --issue <map> --section "Decisions so far"', 'trailhead-dashboard.js', '--set-last-audit', 'fully generated'],
|
|
20
|
+
'trailhead-work/references/ticket-engines.md': ['trailhead-issue-body.js append --issue <map> --section "Decisions so far"', 'trailhead-dashboard.js', 'trailhead-work-state.js clear --repo <working-root>'],
|
|
21
|
+
'trailhead-work/SKILL.md': ['trailhead-issue-body.js append --issue <map> --section "Decisions so far"', 'trailhead-dashboard.js', 'trailhead-work-state.js clear --repo <working-root>',
|
|
22
|
+
'trailhead-issue-body.js remove --issue <map> --section "Not yet specified" --match "<unique text of the entry>"'],
|
|
23
|
+
'_shared/session-handoff.md': ['trailhead-work-state.js clear --repo <working-root>', 'removes both'],
|
|
24
|
+
'_shared/teamwork.md': ['trailhead-issue-body.js append --issue <map> --section "Decisions so far"', 'sessionTicket', 'when the helper is missing'],
|
|
25
|
+
'trailhead-view/SKILL.md': ['trailhead-dashboard.js', '--dry-run'],
|
|
26
|
+
'trailhead-manage/references/auditing.md': ['trailhead-dashboard.js --set-last-audit'],
|
|
27
|
+
'_shared/configuration-reference.md': ['trailhead-issue-body.js replace --issue <conventions> --section :preamble'],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (const [rel, needles] of Object.entries(EXPECT)) {
|
|
31
|
+
const text = read(rel);
|
|
32
|
+
for (const needle of needles) ok(`${rel} names: ${needle}`, text.includes(needle));
|
|
33
|
+
ok(`${rel} has no em-dash`, !text.includes('—'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`resolve-helpers-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #244: the ticket-start scope confirm is skipped on a ticket this session
|
|
3
|
+
// already shaped. The rule is stated once (Work the map step 3); the quick bullet and the
|
|
4
|
+
// choices table only point at it.
|
|
5
|
+
// Run: node scope-confirm-skip-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
|
|
11
|
+
const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
|
|
12
|
+
|
|
13
|
+
let passed = 0;
|
|
14
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
15
|
+
|
|
16
|
+
const ANCHOR = 'Skip the confirm on a ticket this session already shaped';
|
|
17
|
+
const DEFINITION = `**${ANCHOR}.**`;
|
|
18
|
+
|
|
19
|
+
const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
20
|
+
e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]);
|
|
21
|
+
|
|
22
|
+
// 1. the bold definition exists exactly once, in trailhead-work/SKILL.md
|
|
23
|
+
const defs = walk(skills).filter((f) => fs.readFileSync(f, 'utf8').includes(DEFINITION));
|
|
24
|
+
ok('definition appears in exactly one file', defs.length === 1);
|
|
25
|
+
ok('definition lives in trailhead-work/SKILL.md', defs[0] === path.join(skills, 'trailhead-work', 'SKILL.md'));
|
|
26
|
+
const work = read('trailhead-work/SKILL.md');
|
|
27
|
+
ok('definition appears once in SKILL.md', work.split(DEFINITION).length === 2);
|
|
28
|
+
|
|
29
|
+
// 2. step 3 carries the normative fragments
|
|
30
|
+
const lines = work.split('\n');
|
|
31
|
+
const s = lines.findIndex((l) => l.startsWith('3. **Brief the ticket and confirm scope'));
|
|
32
|
+
const e = lines.findIndex((l, i) => i > s && l.startsWith('4. '));
|
|
33
|
+
ok('step 3 found', s >= 0 && e > s);
|
|
34
|
+
const step3 = lines.slice(s, e).join('\n');
|
|
35
|
+
ok('step 3 carries the definition', step3.includes(DEFINITION));
|
|
36
|
+
// match the fragments in the rule sub-bullet only, not the whole of step 3
|
|
37
|
+
const rule = lines.slice(s, e).find((l) => l.includes(DEFINITION));
|
|
38
|
+
for (const frag of [
|
|
39
|
+
"from the user's own request for it",
|
|
40
|
+
'a Grilling round on this ticket concluded',
|
|
41
|
+
'created in bulk',
|
|
42
|
+
'earlier session',
|
|
43
|
+
'/clear',
|
|
44
|
+
'resume',
|
|
45
|
+
'one-line',
|
|
46
|
+
'never inferred from tracker metadata',
|
|
47
|
+
'`auto` is unchanged',
|
|
48
|
+
]) ok(`rule carries: ${frag}`, rule.includes(frag));
|
|
49
|
+
|
|
50
|
+
// 3. the quick bullet only points at the rule
|
|
51
|
+
const quick = lines.find((l) => l.startsWith('- **Briefs and confirms scope first'));
|
|
52
|
+
ok('quick bullet found', !!quick);
|
|
53
|
+
ok('quick bullet references the anchor', quick.includes(ANCHOR));
|
|
54
|
+
ok('quick bullet gates the menu on the rule', quick.includes('Whether the menu is posed at all follows'));
|
|
55
|
+
ok('quick "<text>" gets the note, no menu', quick.includes('one-line note, no menu'));
|
|
56
|
+
ok('quick bullet does not restate bulk exclusion', !quick.includes('created in bulk'));
|
|
57
|
+
ok('quick bullet does not restate grill trigger', !quick.includes('a Grilling round on this ticket concluded'));
|
|
58
|
+
|
|
59
|
+
// 4. the choices row only points at the rule
|
|
60
|
+
const row = read('_shared/choices.md').split('\n').find((l) => l.startsWith('| Ticket-start scope confirm'));
|
|
61
|
+
ok('choices row found', !!row);
|
|
62
|
+
ok('choices row references the anchor', row.includes(ANCHOR));
|
|
63
|
+
ok('choices row does not restate bulk exclusion', !row.includes('created in bulk'));
|
|
64
|
+
|
|
65
|
+
// 5. no em-dash in the touched files
|
|
66
|
+
for (const rel of ['trailhead-work/SKILL.md', '_shared/choices.md'])
|
|
67
|
+
ok(`${rel} has no em-dash`, !read(rel).includes('\u2014'));
|
|
68
|
+
ok('this test has no em-dash', !fs.readFileSync(__filename, 'utf8').includes('\u2014'));
|
|
69
|
+
|
|
70
|
+
console.log(`scope-confirm-skip-docs.test.js: ${passed} checks passed`);
|
package/bin/trailhead.js
CHANGED
|
@@ -253,9 +253,9 @@ const HOOK_FILES = [...BASH_GUARD_MODULES, 'trailhead-bash-guards.js', 'trailhea
|
|
|
253
253
|
// settings.json / hooks.json (everything but the require()d guard modules).
|
|
254
254
|
const ENTRYPOINT_FILES = HOOK_FILES.filter((f) => !BASH_GUARD_MODULES.includes(f));
|
|
255
255
|
// Runtime libs the hook scripts require (trailhead-commit-guard.js does
|
|
256
|
-
// require('./lib/commit-message-check.js');
|
|
257
|
-
//
|
|
258
|
-
// follow-up; trailhead-body-guard.js and trailhead-secret-guard.js both do
|
|
256
|
+
// require('./lib/commit-message-check.js'); several guards (grep `lib/shell-scan`:
|
|
257
|
+
// search, secret-read, secret, install, lib/unmodelled.js) do
|
|
258
|
+
// require('./lib/shell-scan.js'), #135 follow-up; trailhead-body-guard.js and trailhead-secret-guard.js both do
|
|
259
259
|
// require('./lib/gh-subcommand.js'), #153 follow-up; trailhead-secret-guard.js
|
|
260
260
|
// also does require('./lib/body-sources.js'), #210; trailhead-check-update.js
|
|
261
261
|
// does require('./lib/model-defaults-review.js'), which in turn reads the
|