session-orchestrator 3.20.0 → 3.22.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/000-session-orchestrator.mdc +3 -2
- package/.cursor/rules/030-wave-execution.mdc +10 -8
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +8 -8
- package/CHANGELOG.md +515 -0
- package/README.md +16 -11
- package/agents/analyst.md +1 -1
- package/agents/architect-reviewer.md +1 -1
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +1 -1
- package/agents/dialectic-deriver.md +1 -1
- package/agents/docs-writer.md +1 -1
- package/agents/memory-proposal-collector.md +7 -5
- package/agents/qa-strategist.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +42 -1
- package/agents/skill-applied-judge.md +1 -1
- package/agents/test-writer.md +1 -1
- package/agents/ui-developer.md +1 -1
- package/agents/ux-evaluator.md +1 -1
- package/commands/eli5.md +33 -0
- package/commands/release.md +62 -0
- package/commands/test.md +2 -2
- package/docs/components.md +6 -5
- package/docs/migration-v3.md +9 -6
- package/docs/persona-panel.md +3 -1
- package/docs/scope-collision-guard.md +167 -0
- package/docs/session-config-reference.md +31 -8
- package/hooks/_lib/lock-bootstrap.mjs +19 -13
- package/hooks/enforce-scope.mjs +103 -3
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +21 -1
- package/hooks/on-session-end.mjs +76 -97
- package/hooks/on-session-start.mjs +195 -104
- package/hooks/on-stop.mjs +127 -12
- package/hooks/post-bash-write-verify.mjs +8 -32
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +17 -18
- package/hooks/pre-task-scope-disjoint.mjs +1042 -0
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/pi/prompts/release.md +12 -0
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +10 -9
- package/scripts/emit-session.mjs +42 -0
- package/scripts/export-hw-learnings.mjs +61 -2
- package/scripts/lib/auq/clarity.mjs +1314 -0
- package/scripts/lib/auq/parse.mjs +1006 -0
- package/scripts/lib/auq/schema.mjs +1457 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/backlog-scan.mjs +106 -15
- package/scripts/lib/build-live-signals.mjs +7 -3
- package/scripts/lib/ci-status-banner.mjs +267 -77
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/gates/gate-full.mjs +3 -3
- package/scripts/lib/gates/gate-helpers.mjs +17 -6
- package/scripts/lib/git-config-drift.mjs +471 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
- package/scripts/lib/io.mjs +432 -7
- package/scripts/lib/issue-budget.mjs +63 -9
- package/scripts/lib/learnings/select.mjs +157 -3
- package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
- package/scripts/lib/mirror-issues-banner.mjs +266 -0
- package/scripts/lib/named-vault-resolver.mjs +105 -16
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-cards/schema.mjs +6 -2
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/project-hygiene.mjs +64 -4
- package/scripts/lib/reconcile/renderer.mjs +17 -4
- package/scripts/lib/reconcile/writer.mjs +69 -30
- package/scripts/lib/redact-spans.mjs +89 -0
- package/scripts/lib/resource-probe/evaluate.mjs +330 -149
- package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
- package/scripts/lib/resource-probe.mjs +18 -2
- package/scripts/lib/scope-baseline.mjs +77 -17
- package/scripts/lib/scope-gate.mjs +658 -0
- package/scripts/lib/secret-masker.mjs +262 -0
- package/scripts/lib/session-lock.mjs +34 -10
- package/scripts/lib/session-registry.mjs +9 -1
- package/scripts/lib/spiral-carryover.mjs +23 -2
- package/scripts/lib/state-md/mission-status.mjs +164 -58
- package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
- package/scripts/lib/validate/check-agents.mjs +77 -5
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-commands.mjs +2 -20
- package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +48 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +185 -17
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/validate/check-skills.mjs +191 -0
- package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
- package/scripts/lib/validate/check-unwired-features.mjs +219 -11
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
- package/scripts/lib/validate/frontmatter-block.mjs +61 -0
- package/scripts/lib/validate/tier-inference.mjs +46 -8
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vault-mirror/namespace.mjs +146 -1
- package/scripts/lib/vault-mirror/process.mjs +264 -31
- package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
- package/scripts/lib/vault-status/board-writer.mjs +300 -56
- package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
- package/scripts/lib/vcs-repo-spec.mjs +680 -30
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/materialize-wave-scope.mjs +281 -0
- package/scripts/print-learnings-index.mjs +30 -3
- package/scripts/release.mjs +983 -107
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/site-numbers.mjs +1049 -0
- package/scripts/validate-plugin.mjs +64 -0
- package/scripts/validate-wave-scope.mjs +286 -12
- package/scripts/vault-backfill.mjs +32 -5
- package/scripts/vault-mirror.mjs +26 -1
- package/skills/_shared/monitor-patterns.md +24 -4
- package/skills/_shared/parallel-aware-auq.md +30 -24
- package/skills/_shared/parallel-aware-preamble.md +31 -2
- package/skills/_shared/state-ownership.md +49 -6
- package/skills/bootstrap/SKILL.md +2 -1
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/claude-md-drift-check/SKILL.md +9 -1
- package/skills/debug/SKILL.md +4 -1
- package/skills/discovery/SKILL.md +28 -24
- package/skills/discovery/issue-templates.md +4 -4
- package/skills/discovery/probes-code.md +2 -2
- package/skills/discovery/probes-feature.md +6 -6
- package/skills/discovery/probes-infra.md +2 -2
- package/skills/discovery/probes-session.md +5 -5
- package/skills/dispatcher/SKILL.md +10 -1
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/frontmatter-guard/SKILL.md +9 -1
- package/skills/gitlab-ops/SKILL.md +73 -59
- package/skills/gitlab-portfolio/SKILL.md +10 -1
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/memory-cleanup/SKILL.md +20 -7
- package/skills/npm-publish/SKILL.md +23 -51
- package/skills/peekaboo-driver/SKILL.md +3 -3
- package/skills/persona-panel/SKILL.md +3 -1
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +42 -12
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +3 -3
- package/skills/repo-audit/SKILL.md +10 -1
- package/skills/session-end/SKILL.md +97 -22
- package/skills/session-end/metrics-collection.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +37 -2
- package/skills/session-end/session-metrics-write.md +4 -10
- package/skills/session-plan/SKILL.md +2 -2
- package/skills/session-plan/wave-template.md +1 -1
- package/skills/session-start/SKILL.md +82 -36
- package/skills/session-start/phase-2-5-docs-planning.md +8 -8
- package/skills/session-start/phase-4-5-resource-health.md +82 -19
- package/skills/session-start/soul.md +110 -0
- package/skills/spinout/SKILL.md +5 -1
- package/skills/sunset-review/SKILL.md +11 -1
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/tmux-layout/SKILL.md +7 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/vault-mirror/SKILL.md +10 -1
- package/skills/vault-sync/SKILL.md +10 -1
- package/skills/vault-sync/validator.mjs +55 -6
- package/skills/wave-executor/wave-loop.md +64 -12
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/scripts/lib/mission-status-schema.mjs +0 -114
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-auq-clarity.mjs — Ratchet guard for the AUQ question corpus (#1107).
|
|
3
|
+
//
|
|
4
|
+
// ## Why this file exists
|
|
5
|
+
//
|
|
6
|
+
// Wave 2 of #1107 built a deterministic measurer for the operator-facing
|
|
7
|
+
// AskUserQuestion corpus (`scripts/lib/auq/` + `scripts/auq-audit.mjs`); wave 3
|
|
8
|
+
// used it to bring the corpus from 21/72 to 72/72 clean. The measurer had zero
|
|
9
|
+
// callers — no npm script, no gate, no skill phase invoked it. That is this
|
|
10
|
+
// repo's documented systemic failure class ("built, documented, tested, never
|
|
11
|
+
// switched on"), and without a caller the corpus silently rots back.
|
|
12
|
+
//
|
|
13
|
+
// This validator is that caller. It is a RATCHET, not a cleanup order: the
|
|
14
|
+
// corpus stands at 0 broken hurdles today, so the check is green today and goes
|
|
15
|
+
// red exactly when someone lands a NEW question that breaks a hard limit.
|
|
16
|
+
//
|
|
17
|
+
// ## What it gates on — and, deliberately, what it does not
|
|
18
|
+
//
|
|
19
|
+
// It gates on the two HARD LIMITS only (`HURDLES` in scripts/lib/auq/schema.mjs):
|
|
20
|
+
//
|
|
21
|
+
// H1 header at most 12 Unicode CODE POINTS (the tool truncates past that —
|
|
22
|
+
// the operator never sees the rest)
|
|
23
|
+
// H2 2-4 options PER QUESTION (never per block), and a `(Recommended)`
|
|
24
|
+
// marker only on index 0
|
|
25
|
+
//
|
|
26
|
+
// It does NOT gate on the eight weighted criteria K1-K8 or on the resulting
|
|
27
|
+
// score. Their measured false-positive rates run 14%-25% (module head of
|
|
28
|
+
// scripts/lib/auq/clarity.mjs); a gate built on them would object to roughly
|
|
29
|
+
// every fourth CORRECT question, and a validator that cries wolf gets switched
|
|
30
|
+
// off — taking every true finding with it. H1 and H2 are the only two with a
|
|
31
|
+
// measured false-positive rate of 0%. That asymmetry is the whole design:
|
|
32
|
+
// a narrow gate that survives beats a broad gate that gets disabled.
|
|
33
|
+
//
|
|
34
|
+
// Score, grades and the K1-K8 findings remain available on demand via
|
|
35
|
+
// `node scripts/auq-audit.mjs .` — reported, never enforced.
|
|
36
|
+
//
|
|
37
|
+
// ## It measures nothing itself
|
|
38
|
+
//
|
|
39
|
+
// Every judgement comes from the existing modules: `parseRepo()` finds the
|
|
40
|
+
// templates (across ALL six populations, including `.cursor/rules/*.mdc`, which
|
|
41
|
+
// is the only surface a Cursor operator ever sees), `scoreBlocks()` scores them,
|
|
42
|
+
// and `HURDLES` names the limits. A second implementation of the criteria here
|
|
43
|
+
// would be the start of divergence — the two copies would disagree the first
|
|
44
|
+
// time a threshold moves, and the disagreement would be silent.
|
|
45
|
+
//
|
|
46
|
+
// Direct import rather than spawning `scripts/auq-audit.mjs --json`: the audit
|
|
47
|
+
// CLI emits a >1 MB envelope that would have to be re-parsed, and its exit code
|
|
48
|
+
// speaks a different dialect (3 = hard limit broken under `--strict`) than the
|
|
49
|
+
// 0/1/2 this validator family uses. Importing skips both translations.
|
|
50
|
+
//
|
|
51
|
+
// ## Language
|
|
52
|
+
//
|
|
53
|
+
// Comments and structural output are English, matching the 35 sibling
|
|
54
|
+
// validators in this directory. Quoted finding text is German because it comes
|
|
55
|
+
// verbatim from clarity.mjs, where it is written for the operator on purpose —
|
|
56
|
+
// re-wording it here would be exactly the second copy this file avoids.
|
|
57
|
+
//
|
|
58
|
+
// Usage: check-auq-clarity.mjs <plugin-root> [--file <repo-relative-path>]...
|
|
59
|
+
//
|
|
60
|
+
// `--file` restricts the corpus to the named files and bypasses `git ls-files`.
|
|
61
|
+
// Its purpose is testability: the default enumeration is git-backed, so inside a
|
|
62
|
+
// throwaway fixture directory (no git) the corpus would otherwise be empty. The
|
|
63
|
+
// orchestrator never passes it.
|
|
64
|
+
//
|
|
65
|
+
// Outputs lines of the form " PASS: ..." / " FAIL: ..." / " WARN: ..." plus a
|
|
66
|
+
// "Results: N passed, M failed" line so the validate-plugin orchestrator's
|
|
67
|
+
// PASS:/FAIL: tally counts it.
|
|
68
|
+
//
|
|
69
|
+
// Exit codes:
|
|
70
|
+
// 0 — clean (no question breaks a hard limit)
|
|
71
|
+
// 1 — at least one question breaks a hard limit, or the corpus is empty
|
|
72
|
+
// 2 — tool error (missing arg / unreadable root)
|
|
73
|
+
|
|
74
|
+
import { parseRepo } from '../auq/parse.mjs';
|
|
75
|
+
import { scoreBlocks } from '../auq/clarity.mjs';
|
|
76
|
+
import { HURDLES, HURDLE_IDS, POPULATIONS } from '../auq/schema.mjs';
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Argument parsing
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Splits argv into the plugin root and the optional `--file` restriction list.
|
|
84
|
+
*
|
|
85
|
+
* Deliberately NOT exported: this module's body runs the check on import, so an
|
|
86
|
+
* importing test would trigger a `process.exit()` instead of getting a function.
|
|
87
|
+
* Argument handling is covered through the subprocess, like the sibling checks.
|
|
88
|
+
*
|
|
89
|
+
* @param {string[]} argv - arguments after `node <script>`
|
|
90
|
+
* @returns {{root: string|null, files: string[], error: string|null}}
|
|
91
|
+
*/
|
|
92
|
+
function parseCheckArgs(argv) {
|
|
93
|
+
let root = null;
|
|
94
|
+
const files = [];
|
|
95
|
+
for (let i = 0; i < argv.length; i++) {
|
|
96
|
+
const arg = argv[i];
|
|
97
|
+
if (arg === '--file') {
|
|
98
|
+
const value = argv[i + 1];
|
|
99
|
+
if (value === undefined || value.startsWith('--')) {
|
|
100
|
+
return { root, files, error: '--file needs a repo-relative path' };
|
|
101
|
+
}
|
|
102
|
+
files.push(value);
|
|
103
|
+
i++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (arg.startsWith('--')) return { root, files, error: `unknown flag: ${arg}` };
|
|
107
|
+
if (root === null) root = arg;
|
|
108
|
+
}
|
|
109
|
+
return { root, files, error: null };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const parsed = parseCheckArgs(process.argv.slice(2));
|
|
113
|
+
|
|
114
|
+
if (parsed.error) {
|
|
115
|
+
console.error(`Usage: check-auq-clarity.mjs <plugin-root> [--file <path>]... (${parsed.error})`);
|
|
116
|
+
process.exit(2);
|
|
117
|
+
}
|
|
118
|
+
if (!parsed.root) {
|
|
119
|
+
console.error('Usage: check-auq-clarity.mjs <plugin-root> [--file <path>]...');
|
|
120
|
+
process.exit(2);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let passed = 0;
|
|
124
|
+
let failed = 0;
|
|
125
|
+
let warned = 0;
|
|
126
|
+
|
|
127
|
+
function pass(msg) {
|
|
128
|
+
console.log(` PASS: ${msg}`);
|
|
129
|
+
passed++;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function fail(msg) {
|
|
133
|
+
console.log(` FAIL: ${msg}`);
|
|
134
|
+
failed++;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function warn(msg) {
|
|
138
|
+
console.log(` WARN: ${msg}`);
|
|
139
|
+
warned++;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// Check: AUQ hard limits (H1/H2) across the whole question corpus
|
|
144
|
+
// ============================================================================
|
|
145
|
+
console.log('--- Check: AUQ question corpus hard limits (H1 header, H2 options) ---');
|
|
146
|
+
|
|
147
|
+
let repo;
|
|
148
|
+
try {
|
|
149
|
+
repo = parseRepo({
|
|
150
|
+
repoRoot: parsed.root,
|
|
151
|
+
...(parsed.files.length > 0 ? { files: parsed.files } : {}),
|
|
152
|
+
});
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error(` tool-error: cannot parse the AUQ corpus: ${err?.message ?? String(err)}`);
|
|
155
|
+
process.exit(2);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Read warnings are surfaced, never swallowed: each one is a template that
|
|
159
|
+
// silently left the corpus, and a shrinking corpus is how this guard would fade
|
|
160
|
+
// to a decoy without anyone noticing. WARN (not FAIL) matches the sibling
|
|
161
|
+
// convention and never touches the exit code.
|
|
162
|
+
for (const w of repo.warnings) warn(`AUQ corpus: ${w}`);
|
|
163
|
+
|
|
164
|
+
const scores = scoreBlocks(repo.blocks);
|
|
165
|
+
|
|
166
|
+
// --- Anti-decoy guard -------------------------------------------------------
|
|
167
|
+
// A validator that is wired up but measures nothing is indistinguishable from a
|
|
168
|
+
// green one, and that is precisely the failure this file was written to end. An
|
|
169
|
+
// empty corpus therefore FAILS rather than passing vacuously: it means the
|
|
170
|
+
// enumeration broke (git unavailable, corpus prefixes moved, `--file` pointed
|
|
171
|
+
// outside the corpus), never that the repo is clean.
|
|
172
|
+
if (scores.length === 0) {
|
|
173
|
+
fail(
|
|
174
|
+
'the AUQ corpus is empty — 0 questions found, so this check measured nothing. ' +
|
|
175
|
+
'Either the file enumeration broke (git ls-files unavailable, or scripts/lib/auq/parse.mjs ' +
|
|
176
|
+
'corpus prefixes no longer match the tree) or a --file argument pointed outside the corpus. ' +
|
|
177
|
+
'A silently empty corpus is a decoy gate, not a clean repo.',
|
|
178
|
+
);
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(`Results: ${passed} passed, ${failed} failed, ${warned} warned`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- The two hard limits ----------------------------------------------------
|
|
185
|
+
// Note what is NOT consulted here: `score.points`, `score.grade`, and
|
|
186
|
+
// `score.findings[].severity === 'warn'`. Only `hurdlesBroken` drives the exit
|
|
187
|
+
// code. This is also what makes the `optionCountUnknown` case correct by
|
|
188
|
+
// construction: three-plus templates in the corpus end in an ellipsis rather
|
|
189
|
+
// than a full option list, so their true option count is UNKNOWN. clarity.mjs
|
|
190
|
+
// records that as a warn-level note and deliberately breaks NO hurdle for it
|
|
191
|
+
// ("not checkable" is not "violated"). Keying on `hurdlesBroken` inherits that
|
|
192
|
+
// judgement; keying on findings would invent violations that do not exist.
|
|
193
|
+
const brokenByHurdle = new Map(HURDLE_IDS.map((id) => [id, []]));
|
|
194
|
+
|
|
195
|
+
for (const score of scores) {
|
|
196
|
+
for (const id of score.hurdlesBroken) {
|
|
197
|
+
if (!brokenByHurdle.has(id)) brokenByHurdle.set(id, []);
|
|
198
|
+
brokenByHurdle.get(id).push(score);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The operator-facing reason a question broke a hurdle, taken verbatim from the
|
|
204
|
+
* finding clarity.mjs already produced.
|
|
205
|
+
*
|
|
206
|
+
* Filtered on the finding's OWN `hurdle` tag, not on the hurdle's criterion.
|
|
207
|
+
* The criterion filter this used to carry over-reached: K6 produces four
|
|
208
|
+
* finding classes (description length, label length, payload size, option
|
|
209
|
+
* count) and only the last one breaks H2, so a FAIL line here could name a
|
|
210
|
+
* description that is 186 characters long — a finding that breaks nothing —
|
|
211
|
+
* while the real option-count break sat behind it. The same over-reach was
|
|
212
|
+
* measured in `hooks/pre-auq-clarity.mjs` and is fixed there too; both
|
|
213
|
+
* consumers had reconstructed a mapping that `clarity.mjs` already knew.
|
|
214
|
+
*
|
|
215
|
+
* The criterion path stays as a fallback so an older scorer that emits
|
|
216
|
+
* untagged findings degrades to the previous behaviour instead of producing a
|
|
217
|
+
* silent empty reason. Found by this session's architecture review (W4-Q7).
|
|
218
|
+
*
|
|
219
|
+
* @param {import('../auq/schema.mjs').AuqScore} score
|
|
220
|
+
* @param {string} hurdleId
|
|
221
|
+
* @returns {string}
|
|
222
|
+
*/
|
|
223
|
+
function reasonFor(score, hurdleId) {
|
|
224
|
+
const criterion = HURDLES[hurdleId]?.criterion;
|
|
225
|
+
const tagged = score.findings.filter((f) => f.hurdle === hurdleId && f.severity === 'fail');
|
|
226
|
+
const pool = tagged.length > 0
|
|
227
|
+
? tagged
|
|
228
|
+
: score.findings.filter((f) => f.criterion === criterion && f.severity === 'fail');
|
|
229
|
+
const messages = pool.map((f) => f.message);
|
|
230
|
+
return messages.length > 0 ? messages.join(' ') : '(no message recorded)';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const id of HURDLE_IDS) {
|
|
234
|
+
const offenders = brokenByHurdle.get(id) ?? [];
|
|
235
|
+
const hurdle = HURDLES[id];
|
|
236
|
+
|
|
237
|
+
if (offenders.length === 0) {
|
|
238
|
+
// Deliberately reports the DENOMINATOR too. A "0 violations" line is equally
|
|
239
|
+
// true of a corpus of zero questions, which is the state the anti-decoy
|
|
240
|
+
// guard above exists to catch — printing what was actually measured keeps
|
|
241
|
+
// the two distinguishable at a glance.
|
|
242
|
+
pass(
|
|
243
|
+
`${id} (${hurdle.title}) — 0 of ${scores.length} questions break this limit ` +
|
|
244
|
+
`[${hurdle.rule}]`,
|
|
245
|
+
);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const score of offenders) {
|
|
250
|
+
fail(
|
|
251
|
+
`${score.file}:${score.line} (question ${score.questionIndex + 1}) breaks hard limit ${id} ` +
|
|
252
|
+
`— ${hurdle.title}. ${reasonFor(score, id)} See .claude/rules/ask-via-tool.md (AUQ-003) ` +
|
|
253
|
+
'and run `node scripts/auq-audit.mjs .` for the full report.',
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// --- Corpus census (informational) ------------------------------------------
|
|
259
|
+
// Deliberately NOT routed through pass(): this is bookkeeping, not an audited
|
|
260
|
+
// invariant, and must not inflate the passed count the orchestrator tallies.
|
|
261
|
+
//
|
|
262
|
+
// It is printed because the most plausible way this guard degrades is by
|
|
263
|
+
// quietly narrowing to `.md`. The six populations include `.cursor/rules/*.mdc`
|
|
264
|
+
// — the only surface a Cursor operator ever sees — and runtime questions inside
|
|
265
|
+
// `.mjs`. A checker that read markdown only would hand those a clean bill of
|
|
266
|
+
// health while never opening them; a per-population line makes that visible
|
|
267
|
+
// instead of invisible.
|
|
268
|
+
const census = POPULATIONS.map((p) => `${p}:${repo.corpus[p] ?? 0}`).join(' ');
|
|
269
|
+
console.log(` (corpus: ${scores.length} questions in ${repo.blocks.length} blocks — ${census})`);
|
|
270
|
+
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(`Results: ${passed} passed, ${failed} failed, ${warned} warned`);
|
|
273
|
+
|
|
274
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
@@ -8,6 +8,8 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import yaml from 'js-yaml';
|
|
10
10
|
|
|
11
|
+
import { extractInitialFrontmatter } from './frontmatter-block.mjs';
|
|
12
|
+
|
|
11
13
|
const [, , pluginRoot] = process.argv;
|
|
12
14
|
|
|
13
15
|
if (!pluginRoot) {
|
|
@@ -31,26 +33,6 @@ function fail(msg) {
|
|
|
31
33
|
failed++;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
/**
|
|
35
|
-
* Extract the YAML block at the beginning of a command file.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} content
|
|
38
|
-
* @returns {{ ok: true, yamlText: string } | { ok: false, diagnostic: string }}
|
|
39
|
-
*/
|
|
40
|
-
function extractInitialFrontmatter(content) {
|
|
41
|
-
const lines = content.split(/\r?\n/);
|
|
42
|
-
if (lines[0] !== '---') {
|
|
43
|
-
return { ok: false, diagnostic: 'missing YAML frontmatter opening delimiter' };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const closingDelimiter = lines.indexOf('---', 1);
|
|
47
|
-
if (closingDelimiter === -1) {
|
|
48
|
-
return { ok: false, diagnostic: 'missing YAML frontmatter closing delimiter' };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return { ok: true, yamlText: lines.slice(1, closingDelimiter).join('\n') };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
36
|
/**
|
|
55
37
|
* Parse and validate the frontmatter contract for one command file.
|
|
56
38
|
*
|