@marcomigozzi/trailhead 0.14.0 → 0.16.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 +158 -34
- package/README.md +147 -33
- package/bin/auto-intro-docs.test.js +72 -0
- package/bin/auto-merge-docs.test.js +33 -0
- package/bin/auto-merge-ticket-docs.test.js +32 -0
- package/bin/auto-review-rounds-docs.test.js +45 -0
- package/bin/auto-run-docs.test.js +60 -0
- package/bin/auto-scope-docs.test.js +33 -0
- package/bin/auto-slots-docs.test.js +45 -0
- package/bin/auto-uat-docs.test.js +47 -0
- package/bin/base-preflight-docs.test.js +65 -0
- package/bin/execute-split-docs.test.js +146 -0
- package/bin/grilling-holistic-review-docs.test.js +90 -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 +6 -6
- package/bin/lib/codex-projection.test.js +43 -2
- package/bin/needs-you-docs.test.js +41 -0
- package/bin/pick-stress-test-docs.test.js +105 -0
- package/bin/report-status-docs.test.js +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 +6 -6
- package/bin/trailhead.test.js +355 -16
- 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/commands/auto.md +2 -2
- package/plugins/trailhead/hooks/lib/auto-merge.test.js +467 -0
- package/plugins/trailhead/hooks/lib/auto-queue.test.js +245 -0
- package/plugins/trailhead/hooks/lib/auto-run.test.js +318 -0
- package/plugins/trailhead/hooks/lib/auto-slot.test.js +278 -0
- package/plugins/trailhead/hooks/lib/base-preflight.test.js +513 -0
- package/plugins/trailhead/hooks/lib/checkout-in.test.js +173 -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 +472 -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/merge-ticket.test.js +394 -0
- package/plugins/trailhead/hooks/lib/needs-you.test.js +177 -0
- package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +319 -0
- package/plugins/trailhead/hooks/lib/report-status.test.js +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/statusline-context.test.js +140 -0
- package/plugins/trailhead/hooks/lib/uat-backfill.test.js +150 -0
- package/plugins/trailhead/hooks/lib/uat-followup.test.js +169 -0
- package/plugins/trailhead/hooks/lib/uat-setup.test.js +340 -0
- package/plugins/trailhead/hooks/lib/unblock.test.js +207 -0
- package/plugins/trailhead/hooks/lib/work-state.test.js +45 -0
- package/plugins/trailhead/hooks/mod/README.md +209 -36
- package/plugins/trailhead/hooks/mod/auto-run-view.ts +93 -0
- package/plugins/trailhead/hooks/mod/auto-run.ts +165 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +304 -26
- package/plugins/trailhead/hooks/mod/map-pane.tsx +763 -160
- package/plugins/trailhead/hooks/mod/register.tsx +312 -40
- package/plugins/trailhead/hooks/mod/repo-pin.ts +167 -0
- package/plugins/trailhead/hooks/mod/session-registry.ts +65 -0
- package/plugins/trailhead/hooks/mod/turn-state.ts +14 -0
- package/plugins/trailhead/hooks/mod/whiteboard-follow.ts +98 -0
- package/plugins/trailhead/hooks/mod/work-state-locate.ts +191 -0
- package/plugins/trailhead/hooks/mod/work-state-prompt.ts +13 -15
- package/plugins/trailhead/hooks/mod/work-state-view.ts +38 -5
- package/plugins/trailhead/hooks/mod/worktree-porcelain.ts +17 -0
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +22 -16
- package/plugins/trailhead/hooks/trailhead-install-guard.js +169 -34
- package/plugins/trailhead/hooks/trailhead-install-guard.test.js +180 -1
- package/plugins/trailhead/hooks/trailhead-labels-guard.js +231 -0
- package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +212 -0
- package/plugins/trailhead/hooks/trailhead-search-guard.js +43 -7
- package/plugins/trailhead/hooks/trailhead-search-guard.test.js +66 -2
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +296 -22
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +253 -0
- package/plugins/trailhead/skills/_shared/choices.md +11 -4
- package/plugins/trailhead/skills/_shared/configuration-reference.md +17 -5
- 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 +47 -14
- package/plugins/trailhead/skills/_shared/substrate.md +9 -4
- package/plugins/trailhead/skills/_shared/teamwork.md +17 -8
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +18 -10
- 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/grilling.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +3 -1
- 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 +3 -3
- 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 +2 -2
- 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 +9 -6
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +31 -18
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +70 -22
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +29 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +29 -13
- package/plugins/trailhead/templates/statusline-trailhead.sh +77 -12
- package/plugins/trailhead/templates/trailhead-auto-merge.js +318 -0
- package/plugins/trailhead/templates/trailhead-auto-queue.js +186 -0
- package/plugins/trailhead/templates/trailhead-auto-run.js +294 -0
- package/plugins/trailhead/templates/trailhead-auto-slot.js +239 -0
- package/plugins/trailhead/templates/trailhead-base-preflight.js +305 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +88 -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 +360 -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-merge-ticket.js +366 -0
- package/plugins/trailhead/templates/trailhead-needs-you.js +193 -0
- package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +289 -0
- package/plugins/trailhead/templates/trailhead-report-status.js +274 -0
- package/plugins/trailhead/templates/trailhead-uat-backfill.js +114 -0
- package/plugins/trailhead/templates/trailhead-uat-followup.js +128 -0
- package/plugins/trailhead/templates/trailhead-uat-setup.js +385 -0
- package/plugins/trailhead/templates/trailhead-unblock.js +111 -0
- package/plugins/trailhead/templates/trailhead-work-state.js +25 -7
- package/plugins/trailhead/tests/auto-run-ui.test.ts +444 -0
- package/plugins/trailhead/tests/auto-run-view.test.ts +111 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +2519 -382
- package/plugins/trailhead/tests/map-pane-view.test.ts +600 -18
- package/plugins/trailhead/tests/map-pane-write.test.ts +51 -0
- package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +77 -0
- package/plugins/trailhead/tests/repo-pin.test.ts +123 -0
- package/plugins/trailhead/tests/session-registry.test.ts +50 -0
- package/plugins/trailhead/tests/whiteboard-follow.test.ts +90 -0
- package/plugins/trailhead/tests/work-state-locate.test.ts +33 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +276 -2
- package/plugins/trailhead/tests/work-state-prompt.test.ts +29 -35
- package/plugins/trailhead/tests/work-state-ui.test.ts +73 -1
- package/plugins/trailhead/tests/work-state-view.test.ts +44 -1
- package/plugins/trailhead/tests/work-state-worktree.test.ts +310 -0
- package/plugins/trailhead/types/index.d.ts +62 -3
package/bin/trailhead.js
CHANGED
|
@@ -243,19 +243,19 @@ function claudePaths(configDir) {
|
|
|
243
243
|
settings: path.join(configDir, 'settings.json'),
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
-
// The
|
|
246
|
+
// The six Bash guard modules. They are shipped, but only require()d by the
|
|
247
247
|
// merged trailhead-bash-guards.js (one process per Bash call), never registered
|
|
248
248
|
// on their own. Legacy installs registered each one; the installers strip those.
|
|
249
|
-
const BASH_GUARD_MODULES = ['trailhead-commit-guard.js', 'trailhead-secret-guard.js', 'trailhead-install-guard.js', 'trailhead-search-guard.js', 'trailhead-body-guard.js'];
|
|
249
|
+
const BASH_GUARD_MODULES = ['trailhead-commit-guard.js', 'trailhead-secret-guard.js', 'trailhead-install-guard.js', 'trailhead-search-guard.js', 'trailhead-body-guard.js', 'trailhead-labels-guard.js'];
|
|
250
250
|
// Every hook script copied into the hooks dir (shipped runtime files).
|
|
251
251
|
const HOOK_FILES = [...BASH_GUARD_MODULES, 'trailhead-bash-guards.js', 'trailhead-issue-injection-scanner.js', 'trailhead-secret-read-guard.js', 'trailhead-mockup-link-guard.js', 'trailhead-mockup-link-stop.js', 'trailhead-check-update.js'];
|
|
252
252
|
// The registered entrypoints: the shipped files that must appear in
|
|
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
|
|
@@ -436,7 +436,7 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
436
436
|
|
|
437
437
|
const s = readJSON(P.settings);
|
|
438
438
|
// Migration: an earlier install registered each Bash guard on its own; the
|
|
439
|
-
// merged entrypoint below replaces all
|
|
439
|
+
// merged entrypoint below replaces all six.
|
|
440
440
|
stripLegacyBashGuards(s);
|
|
441
441
|
const added = [
|
|
442
442
|
addHook(s, 'PreToolUse', 'Bash', hookCmd('trailhead-bash-guards.js')),
|
package/bin/trailhead.test.js
CHANGED
|
@@ -118,6 +118,12 @@ ok('codex: commit-msg template projected', fs.existsSync(codexTemplate));
|
|
|
118
118
|
ok('codex: commit-msg template is verbatim (not converted)',
|
|
119
119
|
fs.readFileSync(codexTemplate, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-commit-msg'), 'utf8'));
|
|
120
120
|
|
|
121
|
+
// #257: the hook logic ships as a .cjs beside the sh wrapper.
|
|
122
|
+
const codexCommitMsgCjs = path.join(codexDir, 'skills', 'trailhead', 'templates', 'trailhead-commit-msg.cjs');
|
|
123
|
+
ok('codex: commit-msg .cjs logic projected', fs.existsSync(codexCommitMsgCjs));
|
|
124
|
+
ok('codex: commit-msg .cjs logic is verbatim (not converted)',
|
|
125
|
+
fs.readFileSync(codexCommitMsgCjs, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-commit-msg.cjs'), 'utf8'));
|
|
126
|
+
|
|
121
127
|
// #185: the commit-msg hook sync script ships wholesale with templates/ too.
|
|
122
128
|
const codexCommitMsgSync = path.join(codexDir, 'skills', 'trailhead', 'templates', 'trailhead-commit-msg-sync.js');
|
|
123
129
|
ok('codex: commit-msg-sync script projected', fs.existsSync(codexCommitMsgSync));
|
|
@@ -129,6 +135,24 @@ ok('codex: work-state helper projected', fs.existsSync(codexWorkState));
|
|
|
129
135
|
ok('codex: work-state helper is byte-identical to source',
|
|
130
136
|
fs.readFileSync(codexWorkState, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-work-state.js'), 'utf8'));
|
|
131
137
|
ok('codex: work-state helper keeps its executable bit', (fs.statSync(codexWorkState).mode & 0o111) !== 0);
|
|
138
|
+
// #242: the isolation copy helper rides in templates/ on Codex too, executable.
|
|
139
|
+
const codexIsolationCopy = path.join(codexDir, 'skills', 'trailhead', 'templates', 'trailhead-isolation-copy.js');
|
|
140
|
+
ok('codex: isolation-copy helper projected', fs.existsSync(codexIsolationCopy));
|
|
141
|
+
ok('codex: isolation-copy helper is byte-identical to source',
|
|
142
|
+
fs.readFileSync(codexIsolationCopy, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-isolation-copy.js'), 'utf8'));
|
|
143
|
+
ok('codex: isolation-copy helper keeps its executable bit', (fs.statSync(codexIsolationCopy).mode & 0o111) !== 0);
|
|
144
|
+
// #243: the stale-base preflight helper rides in templates/ on Codex too, executable.
|
|
145
|
+
const codexBasePreflight = path.join(codexDir, 'skills', 'trailhead', 'templates', 'trailhead-base-preflight.js');
|
|
146
|
+
ok('codex: base-preflight helper projected', fs.existsSync(codexBasePreflight));
|
|
147
|
+
ok('codex: base-preflight helper is byte-identical to source',
|
|
148
|
+
fs.readFileSync(codexBasePreflight, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-base-preflight.js'), 'utf8'));
|
|
149
|
+
ok('codex: base-preflight helper keeps its executable bit', (fs.statSync(codexBasePreflight).mode & 0o111) !== 0);
|
|
150
|
+
// #252: the UAT setup helper rides in templates/ on Codex too, executable.
|
|
151
|
+
const codexUatSetup = path.join(codexDir, 'skills', 'trailhead', 'templates', 'trailhead-uat-setup.js');
|
|
152
|
+
ok('codex: uat-setup helper projected', fs.existsSync(codexUatSetup));
|
|
153
|
+
ok('codex: uat-setup helper is byte-identical to source',
|
|
154
|
+
fs.readFileSync(codexUatSetup, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-uat-setup.js'), 'utf8'));
|
|
155
|
+
ok('codex: uat-setup helper keeps its executable bit', (fs.statSync(codexUatSetup).mode & 0o111) !== 0);
|
|
132
156
|
// Il sync del label guard (migrazione all'if a livello di job) viaggia con templates/.
|
|
133
157
|
const codexLabelGuardSync = path.join(codexDir, 'skills', 'trailhead', 'templates', 'trailhead-label-guard-sync.js');
|
|
134
158
|
ok('codex: label-guard-sync script is byte-identical to source', fs.existsSync(codexLabelGuardSync) &&
|
|
@@ -140,6 +164,18 @@ ok('codex: audit-walk script is byte-identical to source',
|
|
|
140
164
|
fs.readFileSync(codexAuditWalk, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-audit-walk.js'), 'utf8'));
|
|
141
165
|
ok('codex: audit-walk script keeps its executable bit', (fs.statSync(codexAuditWalk).mode & 0o111) !== 0);
|
|
142
166
|
|
|
167
|
+
// #253: the Resolve bookkeeping helpers ride in templates/ on Codex too, executable,
|
|
168
|
+
// and find the secret-guard module from their installed location.
|
|
169
|
+
for (const helper of ['trailhead-issue-body.js', 'trailhead-dashboard.js']) {
|
|
170
|
+
const projected = path.join(codexDir, 'skills', 'trailhead', 'templates', helper);
|
|
171
|
+
ok(`codex: ${helper} projected`, fs.existsSync(projected));
|
|
172
|
+
ok(`codex: ${helper} is byte-identical to source`,
|
|
173
|
+
fs.readFileSync(projected, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', helper), 'utf8'));
|
|
174
|
+
ok(`codex: ${helper} keeps its executable bit`, (fs.statSync(projected).mode & 0o111) !== 0);
|
|
175
|
+
}
|
|
176
|
+
ok('codex: the installed issue-body helper finds the secret guard',
|
|
177
|
+
require(path.join(codexDir, 'skills', 'trailhead', 'templates', 'trailhead-issue-body.js')).loadGuard() !== null);
|
|
178
|
+
|
|
143
179
|
ok('codex: no commands dir', !fs.existsSync(path.join(codexDir, 'commands')));
|
|
144
180
|
ok('codex: no settings.json file', !fs.existsSync(path.join(codexDir, 'settings.json')));
|
|
145
181
|
// #46: per-verb discoverability now projects one Codex SKILL per verb
|
|
@@ -210,6 +246,28 @@ for (const cl of ['trailhead', 'trailhead-chart', 'trailhead-work', 'trailhead-v
|
|
|
210
246
|
ok(`source: ${cl}/SKILL.md does NOT duplicate the core-list signature line`, !fs.readFileSync(path.join(sourceSkillsDir, cl, 'SKILL.md'), 'utf8').includes(loadFirstSignature));
|
|
211
247
|
}
|
|
212
248
|
|
|
249
|
+
// --- #237: the shared core is loaded by ABSOLUTE path with the Read tool -----
|
|
250
|
+
const clusters237 = ['trailhead', 'trailhead-chart', 'trailhead-work', 'trailhead-view', 'trailhead-capture', 'trailhead-manage'];
|
|
251
|
+
const loadFirst237 = fs.readFileSync(loadFirstSourcePath, 'utf8');
|
|
252
|
+
for (const cl of clusters237) {
|
|
253
|
+
const src = fs.readFileSync(path.join(sourceSkillsDir, cl, 'SKILL.md'), 'utf8');
|
|
254
|
+
const start = src.indexOf('## Load first');
|
|
255
|
+
const rest = src.slice(start + 3);
|
|
256
|
+
const nextH = rest.search(/\n## /);
|
|
257
|
+
const para = start < 0 ? '' : (nextH < 0 ? rest : rest.slice(0, nextH));
|
|
258
|
+
ok(`#237: ${cl} Load first names the base directory`, /base directory/i.test(para));
|
|
259
|
+
ok(`#237: ${cl} Load first requires an absolute path`, /absolute path/i.test(para));
|
|
260
|
+
ok(`#237: ${cl} Load first requires the Read tool`, /Read tool/.test(para));
|
|
261
|
+
ok(`#237: ${cl} Load first has no bare relative "read ../_shared/load-first.md"`, !/read `\.\.\/_shared\/load-first\.md`/i.test(para));
|
|
262
|
+
ok(`#237: ${cl} SKILL.md has no shell read of a relative _shared/ path`, !/\b(cat|head|tail|sed\s+-n\s+\S+)\s+(\.\.\/)*_shared\//.test(src));
|
|
263
|
+
ok(`#237: ${cl} SKILL.md has no cd-and-read compound`, !/\bcd\s+\S+\s*&&\s*(cat|head|tail|sed|grep)\b/.test(src));
|
|
264
|
+
}
|
|
265
|
+
ok('#237: load-first.md states the absolute-path rule with the Read tool', /absolute path/i.test(loadFirst237) && /Read tool/.test(loadFirst237));
|
|
266
|
+
ok('#237: load-first.md resolves <lib> and <templates> to absolute paths', loadFirst237.includes('`<lib>` (an absolute path') && loadFirst237.includes('`<templates>` (an absolute path)'));
|
|
267
|
+
ok('#237: load-first.md covers every other ../_shared/ or references/ path', loadFirst237.includes('references/') && /every other/i.test(loadFirst237));
|
|
268
|
+
ok('#237: load-first.md no longer says paths are relative to the cluster SKILL.md', !loadFirst237.includes('relative to the cluster `SKILL.md`') && !loadFirst237.includes('relative to the cluster SKILL.md that'));
|
|
269
|
+
ok('#237: load-first.md has no shell read of a relative _shared/ path', !/\b(cat|head|tail|sed\s+-n\s+\S+)\s+(\.\.\/)*_shared\//.test(loadFirst237) && !/\bcd\s+\S+\s*&&\s*(cat|head|tail|sed|grep)\b/.test(loadFirst237));
|
|
270
|
+
|
|
213
271
|
// --- #146: single-sourced ticket-language directive (anti-drift) -------------
|
|
214
272
|
// The standing ticket-language rule (write Issue prose + commit bodies in
|
|
215
273
|
// config.ticket.language, decoupled from the chat language) lives in ONE place,
|
|
@@ -287,6 +345,27 @@ ok('source: capture.md offers /trailhead:quick as the lighter alternative, after
|
|
|
287
345
|
ok('source: capture.md says quick is offered alongside work, never in its place', captureSource.includes('never in its place'));
|
|
288
346
|
ok('source: capture.md keeps /trailhead:work the anchor even for a whiteboard capture', /whiteboard[\s\S]{0,200}the anchor stays `\/trailhead:work <n>`/.test(captureSource));
|
|
289
347
|
|
|
348
|
+
// --- #246: `bug --of <ticket>` inherits the origin's destination ------------
|
|
349
|
+
// Source-level invariant: capture.md's `bug` bullet makes `--of` inherit the origin
|
|
350
|
+
// ticket's map (open) or the whiteboard without the which-map ask, checks the origin
|
|
351
|
+
// map's state before inheriting, falls back to the Map or whiteboard rule on a closed
|
|
352
|
+
// map / absent or contradictory labels, and names the inherited destination and the
|
|
353
|
+
// origin (each by name and number) in the confirmation line. The Map or whiteboard
|
|
354
|
+
// paragraph cross-references the exception (single source stays in the `bug` bullet).
|
|
355
|
+
const captureBugBullet = (captureSource.match(/^- \*\*`bug`\*\*.*$/m) || [''])[0];
|
|
356
|
+
const captureMapParagraph = (captureSource.match(/^\*\*Map or whiteboard:\*\*.*$/m) || [''])[0];
|
|
357
|
+
ok('source: capture.md `bug --of` inherits the origin destination instead of asking', /\*\*`--of` also inherits the origin's destination instead of asking/.test(captureBugBullet));
|
|
358
|
+
ok('source: capture.md `bug --of` lands on an OPEN origin map with no ask, and a closed one falls back',
|
|
359
|
+
/\*\*open\*\* -> land on map `<n>`[\s\S]{0,120}no ask; \*\*closed\*\* -> fall back/.test(captureBugBullet));
|
|
360
|
+
ok('source: capture.md `bug --of` lands on the whiteboard for a whiteboard origin, no ask, with the dashboard refresh',
|
|
361
|
+
/carries `trailhead:whiteboard`[\s\S]{0,80}land on the whiteboard, no ask \(dashboard refresh/.test(captureBugBullet));
|
|
362
|
+
ok('source: capture.md `bug --of` checks the origin map state (--json state) before inheriting', /gh issue view <n> --json state/.test(captureBugBullet));
|
|
363
|
+
ok('source: capture.md `bug --of` falls back to the Map or whiteboard rule on closed map, absent or contradictory labels',
|
|
364
|
+
/[Ff]all back to the existing Map or whiteboard rule[\s\S]{0,600}closed[\s\S]{0,300}neither label[\s\S]{0,300}contradictory/.test(captureBugBullet));
|
|
365
|
+
ok('source: capture.md `bug --of` confirmation names the inherited destination and the origin, each by name and number',
|
|
366
|
+
/[Cc]onfirmation line[\s\S]{0,300}inherited[\s\S]{0,200}by name and number[\s\S]{0,200}origin[\s\S]{0,100}by name and number/.test(captureBugBullet));
|
|
367
|
+
ok('source: capture.md Map or whiteboard paragraph cross-references the `bug --of` ask exception', /`bug --of`[\s\S]{0,200}skip/.test(captureMapParagraph));
|
|
368
|
+
|
|
290
369
|
// --- #179: `quick <n>` on a map ticket is never "off the map" ----------------
|
|
291
370
|
// Source-level invariant: `quick <n>` on an EXISTING ticket does not leave the
|
|
292
371
|
// map (it keeps its trailhead:map-<n> label and sub-issue edge, and dependent-
|
|
@@ -543,6 +622,30 @@ ok('source: trailhead-work SKILL.md work step 1 points to the named-ticket activ
|
|
|
543
622
|
ok('source: trailhead-work SKILL.md quick isolation bullet points to the named-ticket active-map rule',
|
|
544
623
|
namedTicketMapPointer.test(quickIsolationBullet));
|
|
545
624
|
|
|
625
|
+
// --- #234: the ticket-start scope confirm is a host menu, not free text ------
|
|
626
|
+
const workStep3 = unitFrom(workSkillSource, '3. **Brief the ticket and confirm scope', /\n(?:\d+\. |\s*\n)/);
|
|
627
|
+
const quickBriefBullet = unitFrom(workSkillSource, '- **Briefs and confirms scope first', /\n/);
|
|
628
|
+
const choicesScopeSource = fs.readFileSync(path.join(sourceSkillsDir, "_shared", "choices.md"), "utf8");
|
|
629
|
+
ok('source: work step 3 poses the scope confirm as an AskUserQuestion menu, Proceed (Recommended) first, Change something second',
|
|
630
|
+
/`AskUserQuestion`[\s\S]{0,300}\*\*Proceed\*\* \(Recommended\)[\s\S]{0,200}\*\*Change something\*\*/.test(workStep3));
|
|
631
|
+
ok('source: work step 3 offers the delegate option on the scope confirm',
|
|
632
|
+
/\*\*delegate\*\*[\s\S]{0,200}choices\.md/i.test(workStep3));
|
|
633
|
+
ok('source: work step 3 divergent change is a menu (as-is vs separate ticket) without delegate',
|
|
634
|
+
/diverges[\s\S]{0,500}`AskUserQuestion`[\s\S]{0,300}without the delegate option/.test(workStep3));
|
|
635
|
+
ok('source: work step 3 has a plain-text numbered list fallback for hosts without a question tool (Codex)',
|
|
636
|
+
/no structured question tool[\s\S]{0,120}plain-text numbered list/.test(workStep3));
|
|
637
|
+
ok('source: work step 3 keeps the auto sentence (proceeds without stopping to brief)',
|
|
638
|
+
/`auto` proceeds \(takes the delegate option\) without stopping to brief/.test(workStep3));
|
|
639
|
+
ok('source: work step 3 no longer asks the change question as free text',
|
|
640
|
+
!/ask whether they want to change anything/.test(workStep3));
|
|
641
|
+
ok('source: quick brief bullet poses the same menu and keeps a one-line note for quick "<text>"',
|
|
642
|
+
/`AskUserQuestion`/.test(quickBriefBullet) && /\*\*Proceed\*\* \(Recommended\)/.test(quickBriefBullet) &&
|
|
643
|
+
/one-line note/.test(quickBriefBullet) && !/one-line confirmation/.test(quickBriefBullet));
|
|
644
|
+
ok('source: choices.md call-site table has the ticket-start scope confirm row (delegate yes, defer no)',
|
|
645
|
+
/\| Ticket-start scope confirm \(`work`\/`quick` brief\) \| Yes[^|]*\| No[^|]*\|/.test(choicesScopeSource));
|
|
646
|
+
ok('source: choices.md call-site table has the divergent-change row (delegate no, defer no)',
|
|
647
|
+
/\| Divergent change at the brief \(as-is vs separate ticket\) \| No[^|]*\| No[^|]*\|/.test(choicesScopeSource));
|
|
648
|
+
|
|
546
649
|
const inboxSource = fs.readFileSync(path.join(sourceSkillsDir, 'trailhead-chart', 'references', 'inbox.md'), 'utf8');
|
|
547
650
|
ok('source: inbox.md out-of-scope close drops the native sub-issue edge',
|
|
548
651
|
/`trailhead:out-of-scope`, \*\*drop its native sub-issue edge\*\*/.test(inboxSource));
|
|
@@ -580,7 +683,7 @@ for (const phrase of [
|
|
|
580
683
|
'**wait for an explicit answer**',
|
|
581
684
|
'**silence or an ambiguous reply re-asks**',
|
|
582
685
|
'**One-path skip**',
|
|
583
|
-
'**
|
|
686
|
+
'**states the reason in one line**',
|
|
584
687
|
'VERIFY',
|
|
585
688
|
'only for the steps it cannot perform',
|
|
586
689
|
'runs unchanged',
|
|
@@ -654,12 +757,12 @@ ok('source: both Pending-UAT guard lines reference the drive-mode ask', pendingU
|
|
|
654
757
|
// auto.md mentions the ask, AND (plan-review addition) a single localized span
|
|
655
758
|
// states its outcomes TOGETHER: takes it without the confirm gate, picks
|
|
656
759
|
// AI-driven when any step is agent-performable, never fakes a human-only step,
|
|
657
|
-
// and
|
|
760
|
+
// and routes the human-only steps to a follow-up opened before the parent closes (#281).
|
|
658
761
|
const autoSourcePath = path.join(sourceSkillsDir, 'trailhead-work', 'references', 'auto.md');
|
|
659
762
|
const autoSource = fs.readFileSync(autoSourcePath, 'utf8');
|
|
660
763
|
ok('source: auto.md mentions the drive-mode ask', autoSource.includes('drive-mode ask'));
|
|
661
764
|
ok('source: auto.md states the drive-mode-ask outcomes together (grouped, not scattered)',
|
|
662
|
-
/drive-mode ask[\s\S]{0,120}without the confirm gate[\s\S]{0,300}AI-driven[\s\S]{0,200}agent-performable[\s\S]{0,300}never
|
|
765
|
+
/drive-mode ask[\s\S]{0,120}without the confirm gate[\s\S]{0,300}AI-driven[\s\S]{0,200}agent-performable[\s\S]{0,300}never fakes[\s\S]{0,1500}follow-up/.test(autoSource));
|
|
663
766
|
|
|
664
767
|
// choices.md carries the call-site row for the drive-mode ask.
|
|
665
768
|
const choicesSourcePath = path.join(sourceSkillsDir, '_shared', 'choices.md');
|
|
@@ -713,13 +816,95 @@ ok('source: configuration-reference.md acceptance.browser clause does NOT make t
|
|
|
713
816
|
ok('source: configuration-reference.md acceptance.browser clause states the drive-mode ask always comes first',
|
|
714
817
|
/\*\*acceptance\.browser\.\*\*[\s\S]{0,200}drive-mode ask[\s\S]{0,120}always comes first/.test(configReferenceSource));
|
|
715
818
|
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
// matching the wording already used for it at line 29 ("human-only steps").
|
|
819
|
+
// Human-only UAT steps are no longer a set-aside reason (#281): the run-end summary
|
|
820
|
+
// lists the UAT follow-ups opened this run instead.
|
|
719
821
|
const runEndSummarySection = extractSection(autoSource, '## Run-end summary');
|
|
720
822
|
ok('source: auto.md Run-end summary section exists', runEndSummarySection.length > 0);
|
|
721
|
-
ok('source: auto.md Run-end summary "what remains"
|
|
722
|
-
/what remains[\s\S]{0,
|
|
823
|
+
ok('source: auto.md Run-end summary "what remains" lists the UAT follow-ups opened this run',
|
|
824
|
+
/what remains[\s\S]{0,900}UAT follow-ups opened this run/.test(runEndSummarySection));
|
|
825
|
+
|
|
826
|
+
// --- #254: skip option at the drive-mode ask (a waiver, not a deferral) -----
|
|
827
|
+
// The waiver's operational contract is single-sourced in the Drive-mode bullet
|
|
828
|
+
// (acceptance-testing.md); the other call-sites carry only the routing /
|
|
829
|
+
// closure consequence and name the bullet.
|
|
830
|
+
const driveModeMarkerLine = driveModeSpan.split('\n')[0] || '';
|
|
831
|
+
ok('source: Drive-mode marker line names the skip option alongside AI-driven / human-driven',
|
|
832
|
+
driveModeMarkerLine.includes('AI-driven') && driveModeMarkerLine.includes('human-driven') && driveModeMarkerLine.includes('skip (waive acceptance)'));
|
|
833
|
+
const skipSubBullet = driveModeSpan.split('\n').find((l) => l.startsWith(' - **Skip (waive acceptance).**')) || '';
|
|
834
|
+
ok('source: Drive-mode span carries the "Skip (waive acceptance)" sub-bullet', skipSubBullet.length > 0);
|
|
835
|
+
for (const phrase of [
|
|
836
|
+
'waived by the user (unconfirmed)',
|
|
837
|
+
'never a pass',
|
|
838
|
+
'the Pending-UAT guard does not fire',
|
|
839
|
+
'no follow-up',
|
|
840
|
+
'The delegate option never picks skip',
|
|
841
|
+
'`auto` never picks skip',
|
|
842
|
+
'UAT of:',
|
|
843
|
+
'closes as waived',
|
|
844
|
+
'resolution comment',
|
|
845
|
+
'all three start paths',
|
|
846
|
+
]) {
|
|
847
|
+
ok(`source: Skip sub-bullet pins "${phrase}"`, skipSubBullet.includes(phrase));
|
|
848
|
+
}
|
|
849
|
+
const oneAskLine = driveModeSpan.split('\n').find((l) => l.startsWith(' - **One-path skip**')) || '';
|
|
850
|
+
ok('source: one-path skip no longer suppresses the ask outright (drops only the AI-driven option)',
|
|
851
|
+
oneAskLine.includes('drops only the AI-driven option') && oneAskLine.includes('human-driven or skip') && !oneAskLine.includes('skip the ask'));
|
|
852
|
+
ok('source: one-path skip still states the reason in one line, naming the blocking class',
|
|
853
|
+
oneAskLine.includes('**states the reason in one line**') && oneAskLine.includes('blocking class') && !oneAskLine.includes('prefaced by'));
|
|
854
|
+
ok('source: on the reduced ask (no performable step) the delegate option resolves to human-driven, never skip',
|
|
855
|
+
/delegate option resolves to human-driven \(never skip\)/.test(oneAskLine));
|
|
856
|
+
ok('source: Pending-UAT bullet distinguishes a waiver from a deferral', pendingUatLine.includes('a waiver is not a deferral'));
|
|
857
|
+
ok('source: Pending-UAT bullet pins the waiver exception (picked skip, recorded as unconfirmed, nothing opened)',
|
|
858
|
+
/The one exception is an explicit waiver[^.]*\. The human picked skip at the drive-mode ask[^.]*unconfirmed in `VERIFY`[^.]*nothing is opened/.test(pendingUatLine));
|
|
859
|
+
const waiverResolveRe = /resolv(e|ing) on a confirmed pass, or on an explicit waiver if the user skips at the ask/;
|
|
860
|
+
ok('source: Pending-UAT bullet option (a) resolves on a confirmed pass or an explicit waiver',
|
|
861
|
+
waiverResolveRe.test(pendingUatLine) && !pendingUatLine.includes('only on a confirmed pass'));
|
|
862
|
+
ok('source: both ticket-engines Resolve lines let option (a) resolve on a confirmed pass or an explicit waiver',
|
|
863
|
+
guardLines254Early().length === 2 && guardLines254Early().every((l) => waiverResolveRe.test(l) && !l.includes('only on a confirmed pass')));
|
|
864
|
+
ok('source: with-no-ticket preflight-then-ask phrase still occurs exactly twice in ticket-engines.md',
|
|
865
|
+
ticketEnginesSource.split('with no ticket (the stale-base preflight first, then the UAT setup, then the drive-mode ask').length - 1 === 2);
|
|
866
|
+
ok('source: both Verify steps let the user skip, recorded as waived',
|
|
867
|
+
verifyStepLines.length === 2 && verifyStepLines.every((l) => /or skip \(waive acceptance\), recorded as waived/.test(l)));
|
|
868
|
+
ok('source: techniques.md index row and auto.md lead-in name skip',
|
|
869
|
+
/ask who drives \(AI or human, or skip\)/.test(fs.readFileSync(path.join(sourceSkillsDir, '_shared', 'techniques.md'), 'utf8')) &&
|
|
870
|
+
autoSource.includes('drive-mode ask (AI-driven, human-driven, or skip)'));
|
|
871
|
+
|
|
872
|
+
function guardLines254Early() { return ticketEnginesSource.split('\n').filter((l) => l.includes('Pending-UAT guard (before the close)')); }
|
|
873
|
+
const guardLines254 = ticketEnginesSource.split('\n').filter((l) => l.includes('Pending-UAT guard (before the close)'));
|
|
874
|
+
ok('source: both Pending-UAT guard lines say the guard does not fire when acceptance was explicitly waived',
|
|
875
|
+
guardLines254.length === 2 && guardLines254.every((l) => l.includes('explicitly waived')));
|
|
876
|
+
ok('source: both Resolve steps state the waiver in the resolution comment',
|
|
877
|
+
guardLines254.length === 2 && guardLines254.every((l) => /resolution comment states the waiver/.test(l)));
|
|
878
|
+
ok('source: the UAT follow-ups section closes on a confirmed pass or an explicit waiver',
|
|
879
|
+
uatFollowupSection.includes('confirmed pass or an explicit waiver') && uatFollowupSection.includes('closes as waived'));
|
|
880
|
+
const driveRowLine = choicesSource.split('\n').find((l) => /^\|[^\n]*[Dd]rive-mode ask/.test(l)) || '';
|
|
881
|
+
ok('source: choices.md drive-mode row names skip and states delegate never picks it',
|
|
882
|
+
driveRowLine.includes('skip') && /delegate never picks skip/.test(driveRowLine) && driveRowLine.includes('reduced ask') && driveRowLine.includes('human-driven'));
|
|
883
|
+
ok('source: configuration-reference.md acceptance.browser paragraph names skip alongside AI-driven / human-driven',
|
|
884
|
+
/\*\*acceptance\.browser\.\*\*[\s\S]{0,200}human-driven,? or skip/.test(configReferenceSource));
|
|
885
|
+
ok('source: auto.md states auto never picks skip', /never picks skip[^\n]*waiving acceptance/.test(autoSource));
|
|
886
|
+
|
|
887
|
+
// Docs mirror: README, README.it, site (waiver recorded as unconfirmed; no unconditional "never closes").
|
|
888
|
+
ok('README.md acceptance testing section names the waiver (skip) recorded as unconfirmed',
|
|
889
|
+
/\*\*Skip is a waiver, not a deferral\*\*[^\n]{0,80}`waived by the user \(unconfirmed\)`, never as a pass/.test(readmeSource));
|
|
890
|
+
ok('README.md no-performable-step case asks human-driven or skip', /human-driven\*\* or \*\*skip\*\*/.test(readmeSource));
|
|
891
|
+
ok('README.md point 6 allows an explicit waiver', /never closes on a UAT nobody ran unless you explicitly waive it/.test(readmeSource));
|
|
892
|
+
const siteAcceptanceSource = fs.readFileSync(path.join(repoRoot, 'site', 'src', 'content', 'docs', 'docs', 'acceptance-testing.md'), 'utf8');
|
|
893
|
+
ok('docs: auto never-picks-skip antecedent is the auto run, in all three docs',
|
|
894
|
+
/the parent ticket resolves; `\/trailhead:auto` never picks skip/.test(readmeSource) && /the parent ticket resolves; `\/trailhead:auto` never picks skip/.test(siteAcceptanceSource) && /il ticket genitore si risolve; `\/trailhead:auto` non sceglie mai skip/.test(readmeItSource));
|
|
895
|
+
ok('README.md build row and acceptance.browser row name skip',
|
|
896
|
+
/🔨 `build`[\s\S]{0,500}or skip/.test(readmeSource) && /`acceptance\.browser`[\s\S]{0,300}or skip/.test(readmeSource));
|
|
897
|
+
ok('README.it.md acceptance testing section names the waiver (skip) recorded as unconfirmed',
|
|
898
|
+
/\*\*Skip è una rinuncia, non un rinvio\*\*[^\n]{0,160}`waived by the user \(unconfirmed\)`, cioè non confermata/.test(readmeItSource));
|
|
899
|
+
ok('README.it.md no-performable-step case asks human-driven o skip', /human-driven\*\* o \*\*skip\*\*/.test(readmeItSource));
|
|
900
|
+
ok('README.it.md point 6 allows an explicit waiver', /non si chiude mai su una UAT che nessuno ha eseguito, salvo che tu ci rinunci esplicitamente/.test(readmeItSource));
|
|
901
|
+
ok('README.it.md build row and acceptance.browser row name skip',
|
|
902
|
+
/🔨 `build`[\s\S]{0,500}o skip/.test(readmeItSource) && /`acceptance\.browser`[\s\S]{0,300}o skip/.test(readmeItSource));
|
|
903
|
+
ok('site acceptance-testing.md names the waiver (skip) recorded as unconfirmed', /\*\*Skip is a waiver, not a deferral\*\*[^\n]{0,80}`waived by the user \(unconfirmed\)`, never as a pass/.test(siteAcceptanceSource));
|
|
904
|
+
ok('site acceptance-testing.md no-performable-step case asks human-driven or skip', /human-driven\*\* or \*\*skip\*\*/.test(siteAcceptanceSource));
|
|
905
|
+
ok('site acceptance-testing.md point 6 allows an explicit waiver', /never closes on a UAT nobody ran unless you explicitly waive it/.test(siteAcceptanceSource));
|
|
906
|
+
ok('site ticket-types.md build row and configuration.mdx acceptance.browser row name skip',
|
|
907
|
+
/`build`[\s\S]{0,500}or skip/.test(ticketTypesSource) && /`acceptance\.browser`[\s\S]{0,300}or skip/.test(configurationMdxSource));
|
|
723
908
|
|
|
724
909
|
// --- codex hooks (#29) --------------------------------------------------------
|
|
725
910
|
const codexHooksJsonPath = path.join(codexDir, 'hooks.json');
|
|
@@ -727,10 +912,10 @@ ok('codex: hooks.json exists', fs.existsSync(codexHooksJsonPath));
|
|
|
727
912
|
const codexHooksJson = JSON.parse(fs.readFileSync(codexHooksJsonPath, 'utf8'));
|
|
728
913
|
const codexHooksJsonStr = JSON.stringify(codexHooksJson);
|
|
729
914
|
ok('codex: hooks.json registers the merged Bash guards under PreToolUse', (codexHooksJson.hooks.PreToolUse || []).some((g) => g.matcher === 'Bash' &&
|
|
730
|
-
(g.hooks || []).some((h) => h.command.includes('trailhead-bash-guards.js') && h.command.endsWith(' --guards=commit,secret,install,body'))));
|
|
731
|
-
ok('codex: hooks.json registers none of the per-guard scripts on their own', !/trailhead-(commit|secret|install|search|body)-guard\.js/.test(codexHooksJsonStr));
|
|
732
|
-
ok('codex: the
|
|
733
|
-
['commit', 'secret', 'install', 'body'].every((n) => fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', `trailhead-${n}-guard.js`))) &&
|
|
915
|
+
(g.hooks || []).some((h) => h.command.includes('trailhead-bash-guards.js') && h.command.endsWith(' --guards=commit,secret,install,body,labels'))));
|
|
916
|
+
ok('codex: hooks.json registers none of the per-guard scripts on their own', !/trailhead-(commit|secret|install|search|body|labels)-guard\.js/.test(codexHooksJsonStr));
|
|
917
|
+
ok('codex: the six guard modules the merged hook requires are still shipped (no search)',
|
|
918
|
+
['commit', 'secret', 'install', 'body', 'labels'].every((n) => fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', `trailhead-${n}-guard.js`))) &&
|
|
734
919
|
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'trailhead-bash-guards.js')));
|
|
735
920
|
ok('codex: hooks.json registers injection-scanner under PostToolUse', (codexHooksJson.hooks.PostToolUse || []).some((g) => (g.hooks || []).some((h) => h.command.includes('trailhead-issue-injection-scanner.js'))));
|
|
736
921
|
ok('codex: hooks.json registers check-update under SessionStart', (codexHooksJson.hooks.SessionStart || []).some((g) => (g.hooks || []).some((h) => h.command.includes('trailhead-check-update.js'))));
|
|
@@ -851,6 +1036,25 @@ ok('codex: every trailhead-*.toml is pin-less when models.codex.* is unset', (()
|
|
|
851
1036
|
return tomls.every((f) => !/^model = /m.test(fs.readFileSync(path.join(dir, f), 'utf8')));
|
|
852
1037
|
})());
|
|
853
1038
|
|
|
1039
|
+
// #250: the reviewing block (single source scripts/agent-blocks.md) is projected
|
|
1040
|
+
// verbatim into the two reviewing agents' TOMLs and into none of the other six.
|
|
1041
|
+
{
|
|
1042
|
+
const sync250 = require(path.join(repoRoot, 'scripts', 'sync-agent-blocks.js'));
|
|
1043
|
+
const reviewingBlock250 = sync250.parseSource(fs.readFileSync(path.join(repoRoot, 'scripts', 'agent-blocks.md'), 'utf8')).reviewing;
|
|
1044
|
+
ok('#250: the source defines a reviewing block', typeof reviewingBlock250 === 'string' && reviewingBlock250.length > 0);
|
|
1045
|
+
const agentsDir250 = path.join(unsetCodexHomeDir, 'agents');
|
|
1046
|
+
const reviewingNames250 = new Set(['trailhead-verify', 'trailhead-review']); // the code-review agent projects as trailhead-review.toml
|
|
1047
|
+
const tomls250 = fs.readdirSync(agentsDir250).filter((f) => /^trailhead-.*\.toml$/.test(f));
|
|
1048
|
+
ok('#250: all 8 TOMLs are checked', tomls250.length === 8);
|
|
1049
|
+
for (const f of tomls250) {
|
|
1050
|
+
const name = f.replace(/\.toml$/, '');
|
|
1051
|
+
const text = fs.readFileSync(path.join(agentsDir250, f), 'utf8');
|
|
1052
|
+
const has = typeof reviewingBlock250 === 'string' && reviewingBlock250.length > 0 && text.includes(reviewingBlock250);
|
|
1053
|
+
ok(`#250: ${f} ${reviewingNames250.has(name) ? 'carries' : 'does not carry'} the reviewing block verbatim`,
|
|
1054
|
+
has === reviewingNames250.has(name));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
854
1058
|
// --- codex migration (#46): install over old prompt shims sweeps them --------
|
|
855
1059
|
const migCodexDir = mktmp();
|
|
856
1060
|
fs.mkdirSync(path.join(migCodexDir, 'prompts'), { recursive: true });
|
|
@@ -884,7 +1088,7 @@ ok('codex uninstall: cluster trailhead-view gone', !fs.existsSync(path.join(code
|
|
|
884
1088
|
ok('codex uninstall: hooks.json no longer contains trailhead commands', (() => {
|
|
885
1089
|
const h = JSON.parse(fs.readFileSync(codexHooksJsonPath, 'utf8'));
|
|
886
1090
|
const str = JSON.stringify(h);
|
|
887
|
-
return !str.includes('trailhead-bash-guards.js') && !/trailhead-(commit|secret|install|search|body)-guard\.js/.test(str) &&
|
|
1091
|
+
return !str.includes('trailhead-bash-guards.js') && !/trailhead-(commit|secret|install|search|body|labels)-guard\.js/.test(str) &&
|
|
888
1092
|
!str.includes('trailhead-issue-injection-scanner.js') && !str.includes('trailhead-check-update.js');
|
|
889
1093
|
})());
|
|
890
1094
|
|
|
@@ -932,6 +1136,12 @@ ok('claude: hooks/lib/model-defaults.json exists (#186)',
|
|
|
932
1136
|
// #202: the mod is not delivered by the npm installer yet.
|
|
933
1137
|
ok('claude: no hooks/mod/ dir in the npm install (#202)', !fs.existsSync(path.join(claudeDir, 'hooks', 'mod')));
|
|
934
1138
|
|
|
1139
|
+
// #257: the hook logic ships as a .cjs beside the sh wrapper.
|
|
1140
|
+
const claudeCommitMsgCjs = path.join(claudeDir, 'trailhead', 'templates', 'trailhead-commit-msg.cjs');
|
|
1141
|
+
ok('claude: commit-msg .cjs logic projected', fs.existsSync(claudeCommitMsgCjs));
|
|
1142
|
+
ok('claude: commit-msg .cjs logic is byte-identical to source',
|
|
1143
|
+
fs.readFileSync(claudeCommitMsgCjs, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-commit-msg.cjs'), 'utf8'));
|
|
1144
|
+
|
|
935
1145
|
// #185: the commit-msg hook sync script ships wholesale with templates/ too.
|
|
936
1146
|
const claudeCommitMsgSync = path.join(claudeDir, 'trailhead', 'templates', 'trailhead-commit-msg-sync.js');
|
|
937
1147
|
ok('claude: commit-msg-sync script projected', fs.existsSync(claudeCommitMsgSync));
|
|
@@ -944,6 +1154,17 @@ const claudeAuditWalk = path.join(claudeDir, 'trailhead', 'templates', 'trailhea
|
|
|
944
1154
|
ok('claude: audit-walk script projected', fs.existsSync(claudeAuditWalk));
|
|
945
1155
|
ok('claude: audit-walk script is byte-identical to source',
|
|
946
1156
|
fs.readFileSync(claudeAuditWalk, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-audit-walk.js'), 'utf8'));
|
|
1157
|
+
// #253: the Resolve bookkeeping helpers are installed, and (npm layout: templates at
|
|
1158
|
+
// <config>/trailhead/templates, hooks at <config>/hooks) find the secret guard.
|
|
1159
|
+
for (const helper of ['trailhead-issue-body.js', 'trailhead-dashboard.js']) {
|
|
1160
|
+
const installed = path.join(claudeDir, 'trailhead', 'templates', helper);
|
|
1161
|
+
ok(`claude: ${helper} installed`, fs.existsSync(installed));
|
|
1162
|
+
ok(`claude: ${helper} is byte-identical to source`,
|
|
1163
|
+
fs.readFileSync(installed, 'utf8') === fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', helper), 'utf8'));
|
|
1164
|
+
ok(`claude: ${helper} keeps its executable bit`, (fs.statSync(installed).mode & 0o111) !== 0);
|
|
1165
|
+
}
|
|
1166
|
+
ok('claude: the installed issue-body helper finds the secret guard',
|
|
1167
|
+
require(path.join(claudeDir, 'trailhead', 'templates', 'trailhead-issue-body.js')).loadGuard() !== null);
|
|
947
1168
|
// Regression: the commit-guard does require('./lib/commit-message-check.js'), so
|
|
948
1169
|
// it only loads if the lib was copied alongside it. Run it with a benign Bash
|
|
949
1170
|
// payload and assert it does not crash with a missing-module error.
|
|
@@ -1083,12 +1304,12 @@ ok('claude: settings.json exists', fs.existsSync(settingsPath));
|
|
|
1083
1304
|
const settingsContent = fs.readFileSync(settingsPath, 'utf8');
|
|
1084
1305
|
ok('claude: settings.json registers the merged Bash guards', settingsContent.includes('trailhead-bash-guards.js'));
|
|
1085
1306
|
ok('claude: settings.json registers none of the per-guard scripts on their own',
|
|
1086
|
-
!/trailhead-(commit|secret|install|search|body)-guard\.js/.test(settingsContent));
|
|
1307
|
+
!/trailhead-(commit|secret|install|search|body|labels)-guard\.js/.test(settingsContent));
|
|
1087
1308
|
ok('claude: exactly one PreToolUse/Bash trailhead-bash-guards registration', (() => {
|
|
1088
1309
|
const s = JSON.parse(settingsContent);
|
|
1089
1310
|
return (s.hooks.PreToolUse || []).flatMap((g) => g.hooks || []).filter((h) => h.command.includes('trailhead-bash-guards.js')).length === 1;
|
|
1090
1311
|
})());
|
|
1091
|
-
ok('claude: hooks/trailhead-bash-guards.js and its
|
|
1312
|
+
ok('claude: hooks/trailhead-bash-guards.js and its six modules exist', ['bash-guards', 'commit-guard', 'secret-guard', 'install-guard', 'search-guard', 'body-guard', 'labels-guard']
|
|
1092
1313
|
.every((n) => fs.existsSync(path.join(claudeDir, 'hooks', `trailhead-${n}.js`))));
|
|
1093
1314
|
// mockup-link-guard: Claude-only PreToolUse(AskUserQuestion) backstop for the
|
|
1094
1315
|
// Prototype "link in the same message as the approval ask" rule.
|
|
@@ -1251,6 +1472,9 @@ ok('source: teamwork.md fallback resolves the hooks dir via rev-parse --git-path
|
|
|
1251
1472
|
teamworkSource.includes('git -C <working-root> rev-parse --git-path hooks'));
|
|
1252
1473
|
ok('source: teamwork.md fallback classifies by the trailhead header line',
|
|
1253
1474
|
teamworkSource.includes('// trailhead commit-msg hook (git).'));
|
|
1475
|
+
ok('source: teamwork.md fallback knows the sh wrapper header and the .cjs logic file (#257)',
|
|
1476
|
+
teamworkSource.includes('# trailhead commit-msg hook (git).') && teamworkSource.includes('trailhead-commit-msg.cjs'));
|
|
1477
|
+
ok('source: substrate-commands.md names the .cjs logic file (#257)', substrateCommandsSource.includes('trailhead-commit-msg.cjs'));
|
|
1254
1478
|
ok('source: teamwork.md fallback checks the executable bit (test -x)',
|
|
1255
1479
|
teamworkSource.includes('test -x'));
|
|
1256
1480
|
ok('source: teamwork.md fallback reads only the first 5 lines',
|
|
@@ -1466,7 +1690,7 @@ const bashGuardCommands = (registry) => (registry.hooks.PreToolUse || [])
|
|
|
1466
1690
|
ok('(#207) codex migration: exit 0', out.status === 0);
|
|
1467
1691
|
const cmds = bashGuardCommands(JSON.parse(fs.readFileSync(hooksJsonFile, 'utf8')));
|
|
1468
1692
|
ok('(#207) codex migration: only the merged entry remains', cmds.length === 1 &&
|
|
1469
|
-
cmds[0].endsWith(' --guards=commit,secret,install,body'));
|
|
1693
|
+
cmds[0].endsWith(' --guards=commit,secret,install,body,labels'));
|
|
1470
1694
|
const once = fs.readFileSync(hooksJsonFile, 'utf8');
|
|
1471
1695
|
runInstaller([`--codex`, `--dir=${mDir}`]);
|
|
1472
1696
|
ok('(#207) codex install twice leaves the same hooks.json', fs.readFileSync(hooksJsonFile, 'utf8') === once);
|
|
@@ -1841,6 +2065,121 @@ ok('source: the declared module file exists, relative to hooks.json (#202)',
|
|
|
1841
2065
|
ok('source: hooks.json keeps its command hooks next to the module (#202)',
|
|
1842
2066
|
['PreToolUse', 'PostToolUse', 'SessionStart'].every((ev) => (pluginHooksJson.hooks?.[ev] || []).length > 0));
|
|
1843
2067
|
|
|
2068
|
+
// --- #259: `trailhead:paused` label marks a paused ticket (pause adds, resume/claim drop) ---
|
|
2069
|
+
const firstUseLine = (substrateCommandsSource.match(/^1\. Create any missing labels.*$/m) || [''])[0];
|
|
2070
|
+
ok('source: first-use setup lists twenty-one labels including trailhead:paused, trailhead:uat and trailhead:needs-you',
|
|
2071
|
+
/all twenty-one:/.test(firstUseLine) && firstUseLine.includes('`trailhead:paused`') && firstUseLine.includes('`trailhead:uat`') && firstUseLine.includes('`trailhead:needs-you`'));
|
|
2072
|
+
ok('source: no stale "eighteen" or "nineteen" label count remains in the shared skills',
|
|
2073
|
+
!/eighteen|nineteen/.test(substrateCommandsSource) && !/eighteen|nineteen/.test(substrateSource));
|
|
2074
|
+
ok('source: substrate-commands.md ensures trailhead:paused idempotently',
|
|
2075
|
+
/gh label create trailhead:paused --color FBCA04 --description "Paused: a PAUSED checkpoint holds its next step" 2>\/dev\/null \|\| true/.test(substrateCommandsSource));
|
|
2076
|
+
ok('source: substrate.md documents trailhead:paused as set by pause, cleared by resume or claim, off the frontier (#263)',
|
|
2077
|
+
/\*\*`trailhead:paused`\*\*[\s\S]{0,500}pause[\s\S]{0,200}resume[\s\S]{0,200}claim[\s\S]{0,400}excluded from the frontier/.test(substrateSource));
|
|
2078
|
+
const pauseBullet = (teamworkSource.match(/^- \*\*Pause\*\*.*$/m) || [''])[0];
|
|
2079
|
+
const resumeBullet = (teamworkSource.match(/^- \*\*Resume\*\*.*$/m) || [''])[0];
|
|
2080
|
+
ok('source: teamwork.md pause adds trailhead:paused (created idempotently) after the PAUSED comment',
|
|
2081
|
+
/post a \*\*`PAUSED`\*\* checkpoint comment[\s\S]{0,1200}add the `trailhead:paused` label[\s\S]{0,120}creating it idempotently first[\s\S]{0,200}after the `PAUSED` comment/.test(pauseBullet));
|
|
2082
|
+
ok('source: teamwork.md resume removes trailhead:paused only after the claim is confirmed',
|
|
2083
|
+
/gh issue edit <n> --remove-label trailhead:paused/.test(resumeBullet) &&
|
|
2084
|
+
/only after the claim is confirmed[\s\S]{0,300}a refused resume leaves the ticket visibly paused/.test(resumeBullet));
|
|
2085
|
+
ok('source: work SKILL.md claim step drops trailhead:paused from a claimed paused ticket',
|
|
2086
|
+
/\*\*Claim\*\* it:[\s\S]{0,1200}carries `trailhead:paused`[\s\S]{0,120}drops it at the claim/.test(workSkillSource));
|
|
2087
|
+
ok('source: work SKILL.md quick isolation bullet drops trailhead:paused at the claim',
|
|
2088
|
+
/^- \*\*Same isolation setup as `work`\.\*\*.*claimed ticket that carries `trailhead:paused` drops it at the claim/m.test(workSkillSource));
|
|
2089
|
+
|
|
2090
|
+
// --- #261: `trailhead:uat` label marks a pending UAT follow-up (marker, not a state) ---
|
|
2091
|
+
{
|
|
2092
|
+
ok('source: substrate-commands.md ensures trailhead:uat idempotently',
|
|
2093
|
+
/gh label create trailhead:uat --color [0-9A-Fa-f]{6} --description "Pending UAT: an acceptance test still to run" 2>\/dev\/null \|\| true/.test(substrateCommandsSource));
|
|
2094
|
+
{
|
|
2095
|
+
const helperSource = fs.readFileSync(path.join(repoRoot, 'plugins', 'trailhead', 'templates', 'trailhead-uat-backfill.js'), 'utf8');
|
|
2096
|
+
const docColor = (substrateCommandsSource.match(/gh label create trailhead:uat --color ([0-9A-Fa-f]{6})/) || [])[1];
|
|
2097
|
+
const helperColor = (helperSource.match(/const COLOR = '([0-9A-Fa-f]{6})'/) || [])[1];
|
|
2098
|
+
ok('source: the backfill helper creates trailhead:uat with the same colour as substrate-commands.md',
|
|
2099
|
+
docColor !== undefined && docColor === helperColor);
|
|
2100
|
+
}
|
|
2101
|
+
ok('source: substrate-commands.md has no create-then-backfill chain and invokes the backfill helper',
|
|
2102
|
+
/trailhead-uat-backfill\.js/.test(substrateCommandsSource) && !/trailhead:uat[^\n]*&&[^\n]*backfill/.test(substrateCommandsSource));
|
|
2103
|
+
ok('source: first-use setup runs the backfill helper after the labels',
|
|
2104
|
+
/^1\. Create any missing labels[\s\S]{0,1500}?trailhead-uat-backfill\.js/m.test(substrateCommandsSource));
|
|
2105
|
+
ok('source: substrate.md documents trailhead:uat as a marker label right after the state labels, not a state, not a frontier filter',
|
|
2106
|
+
/\*\*`trailhead:uat`\*\*[\s\S]{0,700}marker[\s\S]{0,400}not a state[\s\S]{0,400}never a frontier filter/.test(substrateSource)
|
|
2107
|
+
&& substrateSource.indexOf('**`trailhead:uat`**') > substrateSource.indexOf('**`trailhead:paused`**')
|
|
2108
|
+
&& substrateSource.indexOf('**`trailhead:uat`**') < substrateSource.indexOf('**Frontier**'));
|
|
2109
|
+
ok('source: acceptance-testing.md option (b) opens the follow-up with trailhead:uat in the gh issue create --label list, label ensured first',
|
|
2110
|
+
/open a follow-up ticket before that close[\s\S]{0,900}--label[^\n]{0,200}`?trailhead:uat/.test(acceptanceTestingSource)
|
|
2111
|
+
&& /ensur\w* (the )?`?trailhead:uat`? (label )?idempotently/.test(acceptanceTestingSource));
|
|
2112
|
+
ok('source: ticket-engines.md UAT follow-ups section names the label',
|
|
2113
|
+
/UAT follow-up[\s\S]{0,1500}`trailhead:uat`/.test(ticketEnginesSource.split('\n').filter((l) => /^A ticket whose body carries/.test(l)).join('\n')));
|
|
2114
|
+
ok('source: work SKILL.md work-start runs the backfill helper once per clone, marker gated',
|
|
2115
|
+
/trailhead-uat-backfill\.js[^\n]*--marker[^\n]*\.trailhead\/uat-backfill/.test(workSkillSource));
|
|
2116
|
+
ok('source: work SKILL.md passes the backfill marker as an absolute path under the working root',
|
|
2117
|
+
/trailhead-uat-backfill\.js[^\n]*--marker <working-root>\/\.trailhead\/uat-backfill/.test(workSkillSource));
|
|
2118
|
+
ok('source: .gitignore ignores .trailhead/uat-backfill',
|
|
2119
|
+
/^\.trailhead\/uat-backfill$/m.test(fs.readFileSync(path.join(repoRoot, '.gitignore'), 'utf8')));
|
|
2120
|
+
ok('source: work SKILL.md mentions the uat-backfill marker as gitignored per-clone state',
|
|
2121
|
+
/`\.trailhead\/uat-backfill`[^\n]*(gitignored|never committed)/.test(workSkillSource));
|
|
2122
|
+
const producerSources = { 'acceptance-testing.md': acceptanceTestingSource, 'site acceptance-testing.md': siteAcceptanceSource };
|
|
2123
|
+
for (const [name, src] of Object.entries(producerSources)) {
|
|
2124
|
+
ok(`source: ${name} writes the pointer as UAT of: [<parent name>](<parent url>)`,
|
|
2125
|
+
src.includes('UAT of: [<parent name>](<parent url>)') && !/UAT of: <parent name>\(link\)|UAT of: #<n>/.test(src));
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// --- #263: a paused ticket is off the frontier on every surface ---
|
|
2130
|
+
{
|
|
2131
|
+
const viewSkillSource = fs.readFileSync(path.join(sourceSkillsDir, 'trailhead-view', 'SKILL.md'), 'utf8');
|
|
2132
|
+
const siteDocs = path.join(repoRoot, 'site', 'src', 'content', 'docs', 'docs');
|
|
2133
|
+
const siteViewSource = fs.readFileSync(path.join(siteDocs, 'commands', 'view.md'), 'utf8');
|
|
2134
|
+
const siteWorkSource = fs.readFileSync(path.join(siteDocs, 'commands', 'work.md'), 'utf8');
|
|
2135
|
+
const changelogSource = fs.readFileSync(path.join(repoRoot, 'CHANGELOG.md'), 'utf8');
|
|
2136
|
+
const searchExprs = (src) => [...src.matchAll(/--search\s+"([^"]*no:assignee[^"]*)"/g)].map((m) => m[1]);
|
|
2137
|
+
const base = 'no:assignee -label:trailhead:blocked -label:trailhead:unverified -label:trailhead:paused';
|
|
2138
|
+
const wbExcl = `${base} -label:trailhead:whiteboard`;
|
|
2139
|
+
const tally = (exprs) => exprs.reduce((acc, e) => ({ ...acc, [e]: (acc[e] || 0) + 1 }), {});
|
|
2140
|
+
const sameTally = (got, want) => JSON.stringify(Object.entries(got).sort()) === JSON.stringify(Object.entries(want).sort());
|
|
2141
|
+
|
|
2142
|
+
ok('source: substrate-commands.md frontier queries each exclude paused (two repo-wide, four label-scoped or whiteboard or count)',
|
|
2143
|
+
sameTally(tally(searchExprs(substrateCommandsSource)), { [wbExcl]: 2, [base]: 4 }));
|
|
2144
|
+
ok('source: multi-map.md scoped frontier query excludes paused', sameTally(tally(searchExprs(multiMapSource)), { [base]: 1 }));
|
|
2145
|
+
ok('source: trailhead-view SKILL.md whiteboard frontier query excludes paused', sameTally(tally(searchExprs(viewSkillSource)), { [base]: 1 }));
|
|
2146
|
+
ok('source: the cookbook frontier comment says not paused',
|
|
2147
|
+
/# the frontier: open, unassigned, not blocked, not unverified, not paused, one query/.test(substrateCommandsSource));
|
|
2148
|
+
|
|
2149
|
+
ok('source: substrate.md Frontier definition excludes trailhead:paused',
|
|
2150
|
+
/\*\*Frontier\*\* = open, unassigned tickets that are \*\*not\*\* `trailhead:blocked`, \*\*not\*\* `trailhead:unverified` and \*\*not\*\* `trailhead:paused`/.test(substrateSource));
|
|
2151
|
+
const pausedBullet = (substrateSource.match(/^- \*\*`trailhead:paused`\*\*.*$/m) || [''])[0];
|
|
2152
|
+
ok('source: substrate.md paused bullet: excluded from the frontier, taken back with /trailhead:resume',
|
|
2153
|
+
/excluded from the frontier/.test(pausedBullet) && /\/trailhead:resume/.test(pausedBullet));
|
|
2154
|
+
ok('source: the old "does not change the frontier query" wording is gone',
|
|
2155
|
+
!/does not change the frontier query/.test(substrateSource));
|
|
2156
|
+
|
|
2157
|
+
const paused = /^- \*\*In progress\*\*[^\n]*\n- \*\*Paused\*\*[^\n]*\/trailhead:resume <n>`[^\n]*\n- \*\*Blocked\*\*/gm;
|
|
2158
|
+
ok('source: the map and whiteboard renders both list a Paused group between In progress and Blocked',
|
|
2159
|
+
(viewSkillSource.match(paused) || []).length === 2);
|
|
2160
|
+
ok('source: both render Frontier bullets say not paused',
|
|
2161
|
+
(viewSkillSource.match(/^- \*\*Frontier\*\*:[^\n]*not[^\n]*`trailhead:paused`/gm) || []).length === 2);
|
|
2162
|
+
|
|
2163
|
+
ok('source: multi-map.md park & switch resumes with /trailhead:resume only',
|
|
2164
|
+
/Resume later with `\/trailhead:resume`/.test(multiMapSource) && !/just `\/trailhead:work` once that map is active/.test(multiMapSource));
|
|
2165
|
+
|
|
2166
|
+
ok('source: work SKILL.md: bare work takes the paused-excluding frontier, a named work or quick still works a paused ticket',
|
|
2167
|
+
/Otherwise take a frontier ticket[\s\S]{0,600}never a `trailhead:paused` ticket/.test(workSkillSource) &&
|
|
2168
|
+
/\*\*named\*\* ticket may also be a `trailhead:paused` one, which `work <n>` \/ `quick <n>` takes back/.test(workSkillSource) &&
|
|
2169
|
+
/carries `trailhead:paused` drops it at the claim/.test(workSkillSource));
|
|
2170
|
+
|
|
2171
|
+
ok('source: auto.md resumes owned paused tickets in step 2 and no longer says they come back on the frontier',
|
|
2172
|
+
/\*\*each\*\* ticket you own[\s\S]{0,200}`trailhead:paused`/.test(autoSource) &&
|
|
2173
|
+
!/paused\/stuck tickets are back on the frontier/.test(autoSource) && !/paused[^.\n]{0,40}back on the frontier/.test(autoSource));
|
|
2174
|
+
ok('source: site work.md says a paused ticket is off the frontier until resumed',
|
|
2175
|
+
/off the frontier until (it is )?resumed/.test(siteWorkSource));
|
|
2176
|
+
ok('source: site view.md describes the Paused group off the frontier', /Paused/.test(siteViewSource) && /not paused|off the frontier/.test(siteViewSource));
|
|
2177
|
+
const conceptsSource = fs.readFileSync(path.join(siteDocs, 'concepts.md'), 'utf8');
|
|
2178
|
+
ok('source: concepts.md, README.md and README.it.md frontier definitions mention paused',
|
|
2179
|
+
/not paused/.test(conceptsSource) && /not paused/.test(readmeSource) && /in pausa/.test(readmeItSource));
|
|
2180
|
+
ok('source: CHANGELOG Unreleased > Changed has a #263 entry', /### Changed[\s\S]*?\(#263\)/.test(changelogSource.split('## 0.14.0')[0]));
|
|
2181
|
+
}
|
|
2182
|
+
|
|
1844
2183
|
for (const d of tmpDirs) {
|
|
1845
2184
|
fs.rmSync(d, { recursive: true, force: true });
|
|
1846
2185
|
}
|