@event4u/agent-config 10.1.0 → 10.3.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/CHANGELOG.md +110 -167
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/reference-repo.md +62 -0
- package/dist/agent-src/commands/roadmap/create.md +4 -1
- package/dist/agent-src/commands/roadmap/next.md +38 -0
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +22 -1
- package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -1
- package/dist/agent-src/personas/advisors/first-principles.md +6 -0
- package/dist/agent-src/rules/active-remediation.md +53 -2
- package/dist/agent-src/rules/code-provenance.md +66 -4
- package/dist/agent-src/rules/content-quoting-floor.md +6 -0
- package/dist/agent-src/rules/context-hygiene.md +39 -0
- package/dist/agent-src/rules/design-fidelity.md +49 -3
- package/dist/agent-src/rules/token-budget-discipline.md +3 -2
- package/dist/agent-src/rules/token-efficiency.md +22 -0
- package/dist/agent-src/skills/ai-council/SKILL.md +28 -6
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/design-review/references/verification-automation.md +23 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +27 -3
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +30 -0
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +11 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +9 -9
- package/dist/agent-src/skills/skill-writing/SKILL.md +4 -0
- package/dist/agent-src/skills/token-optimizer/SKILL.md +5 -1
- package/dist/agent-src/templates/agent-settings.md +12 -28
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +2 -2
- package/dist/agent-src/templates/persona.md +26 -0
- package/dist/agent-src/templates/roadmaps.md +27 -7
- package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -1
- package/dist/agent-src/templates/skill.md +35 -1
- package/dist/cli/commands/commands.candidates.test.js +257 -0
- package/dist/cli/commands/commands.candidates.test.js.map +1 -0
- package/dist/cli/commands/commands.js +199 -0
- package/dist/cli/commands/commands.js.map +1 -1
- package/dist/cli/main.js +1 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/registry.js +2 -2
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-3JUTT26I.js → chunk-6ENSXSBW.js} +1 -1
- package/dist/cli-delegate/{chunk-TVGXH7VA.js → chunk-6LOPHJLD.js} +1 -1
- package/dist/cli-delegate/{chunk-XT2YTJCZ.js → chunk-AYKQGIBG.js} +4 -0
- package/dist/cli-delegate/{chunk-HJSKTMIF.js → chunk-BV3WR67D.js} +3 -19
- package/dist/cli-delegate/{chunk-HTIU3W56.js → chunk-BWMXLD4B.js} +6 -2
- package/dist/cli-delegate/{chunk-2H5SE7DU.js → chunk-FV34EGEX.js} +166 -39
- package/dist/cli-delegate/{chunk-XTC5ARB6.js → chunk-IR3IVRVC.js} +7 -2
- package/dist/cli-delegate/{chunk-LRXJK6GN.js → chunk-JMQZ7MM5.js} +1 -1
- package/dist/cli-delegate/{chunk-NX5AXSLD.js → chunk-X3VOARZD.js} +3 -3
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +881 -204
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +6 -5
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +44 -9
- package/dist/cli-delegate/cmd_settings_get.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +21 -21
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +204 -44
- package/dist/install/install.mjs +9 -21
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1337 -490
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +25 -2
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/_lib/compile_time_toggles.js +4 -3
- package/dist/scripts/_lib/compile_time_toggles.js.map +1 -1
- package/dist/server/io/yamlIO.js +0 -2
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/schemas/settings.js +3 -9
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/settingsClasses.js +64 -2
- package/dist/shared/settingsClasses.js.map +1 -1
- package/dist/ui/assets/{index-B5qGYOJo.js → index-5fXOHoVh.js} +2 -2
- package/dist/ui/assets/index-5fXOHoVh.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +307 -145
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +54 -0
- package/docs/guidelines/agent-infra/layered-settings.md +1 -2
- package/docs/guidelines/agent-infra/symptom-driven-harvest-loop.md +104 -0
- package/docs/guidelines/design-antipatterns.md +79 -10
- package/docs/guidelines/design-fidelity-mechanics.md +120 -2
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -1
- package/src/config/agent-settings.template.yml +12 -30
- package/src/config/gate-coverage.yml +42 -8
- package/src/config/gate-violation-baselines.json +15 -3
- package/src/config/profiles/balanced.ini +0 -2
- package/src/config/profiles/full.ini +1 -3
- package/src/config/profiles/minimal.ini +0 -2
- package/src/config/review-prompt-binding-baseline.json +19 -0
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/scripts/_cli/cmd_session_recycle.ts +66 -9
- package/src/scripts/_cli/handoff_sessions.ts +3 -2
- package/src/scripts/_dispatch.bash +1 -0
- package/src/scripts/_lib/agent_settings.ts +25 -2
- package/src/scripts/_lib/bench_telegraph.ts +1 -1
- package/src/scripts/_lib/cc_transcript.ts +25 -0
- package/src/scripts/_lib/compile_time_toggles.ts +4 -3
- package/src/scripts/ai_council/clients.ts +243 -34
- package/src/scripts/cache_realization_report.ts +3 -2
- package/src/scripts/check_ci_local_parity.ts +60 -6
- package/src/scripts/check_review_prompt_binding.ts +553 -0
- package/src/scripts/check_rule_projection_integrity.ts +3 -1
- package/src/scripts/check_suppression_hygiene.ts +19 -3
- package/src/scripts/conformance_scan.ts +282 -9
- package/src/scripts/cost/track.mjs +10 -4
- package/src/scripts/council_cli.ts +33 -3
- package/src/scripts/design_slop_rules.test.ts +37 -6
- package/src/scripts/design_slop_rules.ts +150 -22
- package/src/scripts/dispatch_r2_reviewer.ts +15 -1
- package/src/scripts/external_sources_denylist.json +3 -1
- package/src/scripts/hook_manifest.yaml +17 -6
- package/src/scripts/hooks/block_unauthorized_git.ts +54 -8
- package/src/scripts/hooks/delegation_nudge_hook.ts +33 -8
- package/src/scripts/hooks/evidence_independence.ts +61 -9
- package/src/scripts/hooks/session_eol_hook.ts +123 -3
- package/src/scripts/hooks/turn_end_gate_hook.ts +31 -0
- package/src/scripts/hooks/ui_route_nudge_hook.ts +55 -2
- package/src/scripts/lint_design_antipattern_parity.ts +201 -0
- package/src/scripts/lint_harvest_provenance.ts +483 -0
- package/src/scripts/lint_settings_classes.ts +273 -11
- package/src/scripts/lint_skill_router_head.ts +254 -0
- package/src/scripts/lint_symptom_intake.ts +241 -0
- package/src/scripts/report_consultation_rate.ts +128 -2
- package/src/scripts/report_skill_activation.ts +6 -4
- package/src/scripts/report_skill_obligation_violations.ts +2 -1
- package/src/scripts/run_skill_evals.ts +10 -5
- package/src/scripts/schemas/persona.schema.json +8 -0
- package/src/scripts/schemas/provenance-harvest.schema.json +39 -0
- package/src/scripts/sweep_dead_scan_roots.ts +51 -0
- package/src/server/io/yamlIO.ts +0 -2
- package/src/server/schemas/settings.ts +3 -19
- package/src/shared/settingsClasses.ts +93 -2
- package/dist/ui/assets/index-B5qGYOJo.js.map +0 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Warn-only gate: operator symptom entries under `agents/tickets/symptoms/`
|
|
4
|
+
* must be resolved within 30 days of the reported date.
|
|
5
|
+
*
|
|
6
|
+
* "Resolved" means the entry carries exactly one of the two blocks the
|
|
7
|
+
* directory's `README.md` defines — `## confirmed:` (a defect with `file:line`
|
|
8
|
+
* at a pinned commit) or `## null:` (checked, not reproducible, with evidence).
|
|
9
|
+
* A `null:` is a first-class outcome: the point of the gate is that somebody
|
|
10
|
+
* looked, not that a defect was found.
|
|
11
|
+
*
|
|
12
|
+
* **Warn-only by design, and the design is the load-bearing part.** Intake has
|
|
13
|
+
* to stay cheaper than the work it triggers, so an unresolved report is a
|
|
14
|
+
* reminder, never a build failure — a gate that reds CI because a human has not
|
|
15
|
+
* yet investigated a symptom would train people to stop filing symptoms, which
|
|
16
|
+
* is the exact opposite of what the surface is for
|
|
17
|
+
* (`agents/roadmaps/archive/road-to-symptom-driven-harvest-loop.md` Phase 1 Step 2).
|
|
18
|
+
* The only exit-1 path is a dead scan scope.
|
|
19
|
+
*
|
|
20
|
+
* Scope: `agents/tickets/symptoms/*.md`, one level, no recursion.
|
|
21
|
+
* `README.md` and `_`-prefixed files (the template) are counted as scanned but
|
|
22
|
+
* never evaluated — so the gate keeps a non-zero denominator while the surface
|
|
23
|
+
* exists, and does not red the moment the last entry is resolved away.
|
|
24
|
+
*
|
|
25
|
+
* Exit codes: 0 = clean or warnings only, 1 = dead scan scope.
|
|
26
|
+
* `--quiet` is a bare argv membership check, matching the sibling roadmap gates.
|
|
27
|
+
* `--now=YYYY-MM-DD` overrides today (tests and reproducible runs only).
|
|
28
|
+
*/
|
|
29
|
+
import * as fs from 'node:fs';
|
|
30
|
+
import * as path from 'node:path';
|
|
31
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
32
|
+
|
|
33
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
34
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
35
|
+
|
|
36
|
+
const QUIET = process.argv.slice(2).includes('--quiet');
|
|
37
|
+
|
|
38
|
+
const SYMPTOM_DIR = path.join('agents', 'tickets', 'symptoms');
|
|
39
|
+
|
|
40
|
+
/** Days after `reported:` at which an unresolved entry starts warning. */
|
|
41
|
+
export const STALE_AFTER_DAYS = 30;
|
|
42
|
+
|
|
43
|
+
const _HERE = path.resolve(fileURLToPath(import.meta.url));
|
|
44
|
+
|
|
45
|
+
const REPORTED_RE = /^reported:\s*(\d{4}-\d{2}-\d{2})\s*$/m;
|
|
46
|
+
const CONFIRMED_RE = /^##\s+confirmed:\s*$/m;
|
|
47
|
+
const NULL_RE = /^##\s+null:\s*$/m;
|
|
48
|
+
|
|
49
|
+
export interface SymptomFinding {
|
|
50
|
+
readonly file: string;
|
|
51
|
+
readonly kind: 'unresolved' | 'no-reported-date';
|
|
52
|
+
readonly ageDays: number | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Walk up from CWD until a dir containing `agents/tickets` is found. */
|
|
56
|
+
function _repoRoot(): string {
|
|
57
|
+
let cur = process.cwd();
|
|
58
|
+
const chain = [cur];
|
|
59
|
+
for (;;) {
|
|
60
|
+
const parent = path.dirname(cur);
|
|
61
|
+
if (parent === cur) break;
|
|
62
|
+
chain.push(parent);
|
|
63
|
+
cur = parent;
|
|
64
|
+
}
|
|
65
|
+
for (const candidate of chain) {
|
|
66
|
+
try {
|
|
67
|
+
if (fs.statSync(path.join(candidate, 'agents', 'tickets')).isDirectory()) {
|
|
68
|
+
return candidate;
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// keep walking
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return process.cwd();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function _isDir(p: string): boolean {
|
|
78
|
+
try {
|
|
79
|
+
return fs.statSync(p).isDirectory();
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Every `*.md` directly under the symptoms dir, sorted. */
|
|
86
|
+
export function listMarkdown(base: string): string[] {
|
|
87
|
+
if (!_isDir(base)) return [];
|
|
88
|
+
let entries: fs.Dirent[];
|
|
89
|
+
try {
|
|
90
|
+
entries = fs.readdirSync(base, { withFileTypes: true });
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
return entries
|
|
95
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
96
|
+
.map((e) => e.name)
|
|
97
|
+
.sort();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** An entry is a report; `README.md` and `_`-prefixed files are not. */
|
|
101
|
+
export function isEntry(name: string): boolean {
|
|
102
|
+
return name.endsWith('.md') && name !== 'README.md' && !name.startsWith('_');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function _daysBetween(fromIso: string, now: Date): number | null {
|
|
106
|
+
const parsed = Date.parse(`${fromIso}T00:00:00Z`);
|
|
107
|
+
if (Number.isNaN(parsed)) return null;
|
|
108
|
+
return Math.floor((now.getTime() - parsed) / 86_400_000);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function evaluate(text: string, name: string, now: Date): SymptomFinding | null {
|
|
112
|
+
const reported = REPORTED_RE.exec(text);
|
|
113
|
+
if (reported === null) {
|
|
114
|
+
return { file: name, kind: 'no-reported-date', ageDays: null };
|
|
115
|
+
}
|
|
116
|
+
if (CONFIRMED_RE.test(text) || NULL_RE.test(text)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const age = _daysBetween(reported[1] as string, now);
|
|
120
|
+
if (age === null) {
|
|
121
|
+
return { file: name, kind: 'no-reported-date', ageDays: null };
|
|
122
|
+
}
|
|
123
|
+
if (age <= STALE_AFTER_DAYS) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return { file: name, kind: 'unresolved', ageDays: age };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function _now(): Date {
|
|
130
|
+
const flag = process.argv.slice(2).find((a) => a.startsWith('--now='));
|
|
131
|
+
if (flag !== undefined) {
|
|
132
|
+
const parsed = Date.parse(`${flag.slice('--now='.length)}T00:00:00Z`);
|
|
133
|
+
if (!Number.isNaN(parsed)) return new Date(parsed);
|
|
134
|
+
}
|
|
135
|
+
return new Date();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function main(): number {
|
|
139
|
+
const root = _repoRoot();
|
|
140
|
+
const base = path.join(root, SYMPTOM_DIR);
|
|
141
|
+
const names = listMarkdown(base);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// The scanned unit counts README + template too, deliberately: this gate
|
|
145
|
+
// must not go dead the moment the last open entry is resolved, and the
|
|
146
|
+
// directory always carries its convention file while the surface exists.
|
|
147
|
+
assertScanned({
|
|
148
|
+
gate: 'lint_symptom_intake',
|
|
149
|
+
scanned: names.length,
|
|
150
|
+
units: 'symptom file(s)',
|
|
151
|
+
roots: [SYMPTOM_DIR],
|
|
152
|
+
});
|
|
153
|
+
} catch (exc) {
|
|
154
|
+
if (exc instanceof DeadScopeError) {
|
|
155
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
throw exc;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const now = _now();
|
|
162
|
+
const findings: SymptomFinding[] = [];
|
|
163
|
+
let entryCount = 0;
|
|
164
|
+
|
|
165
|
+
// Per-target accounting: README.md and the template are planned too, so the
|
|
166
|
+
// ledger states out loud that they were seen and deliberately not evaluated
|
|
167
|
+
// — an unaccounted `continue` is the silent-skip defect this module catches.
|
|
168
|
+
const ledger = new GateLedger('lint_symptom_intake');
|
|
169
|
+
ledger.plan(names);
|
|
170
|
+
|
|
171
|
+
for (const name of names) {
|
|
172
|
+
if (!isEntry(name)) {
|
|
173
|
+
ledger.outOfScope(name, 'not_applicable_kind');
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
entryCount += 1;
|
|
177
|
+
let text: string;
|
|
178
|
+
try {
|
|
179
|
+
text = fs.readFileSync(path.join(base, name), 'utf-8');
|
|
180
|
+
} catch {
|
|
181
|
+
ledger.skip(name, 'binary_content');
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const finding = evaluate(text, name, now);
|
|
185
|
+
if (finding !== null) {
|
|
186
|
+
findings.push(finding);
|
|
187
|
+
ledger.fail(name, finding.kind);
|
|
188
|
+
} else {
|
|
189
|
+
ledger.complete(name);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (findings.length === 0) {
|
|
194
|
+
ledger.report();
|
|
195
|
+
if (!QUIET) {
|
|
196
|
+
process.stdout.write(
|
|
197
|
+
`✅ lint-symptom-intake: ${entryCount} entry(ies) resolved or within ` +
|
|
198
|
+
`${STALE_AFTER_DAYS} days.\n`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
ledger.report();
|
|
205
|
+
process.stdout.write(`⚠️ lint-symptom-intake: ${findings.length} unresolved symptom entry(ies):\n`);
|
|
206
|
+
for (const f of findings) {
|
|
207
|
+
if (f.kind === 'no-reported-date') {
|
|
208
|
+
process.stdout.write(` ${SYMPTOM_DIR}/${f.file} — no valid \`reported: YYYY-MM-DD\` in frontmatter\n`);
|
|
209
|
+
} else {
|
|
210
|
+
process.stdout.write(
|
|
211
|
+
` ${SYMPTOM_DIR}/${f.file} — reported ${String(f.ageDays)} days ago, ` +
|
|
212
|
+
'no `## confirmed:` or `## null:` block\n',
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
process.stdout.write('\n');
|
|
217
|
+
process.stdout.write(' Add whichever block is true (see agents/tickets/symptoms/README.md):\n');
|
|
218
|
+
process.stdout.write(' • `## confirmed:` — defect + file:line at a pinned commit + owning roadmap\n');
|
|
219
|
+
process.stdout.write(' • `## null:` — what was checked, and why it does not reproduce\n');
|
|
220
|
+
process.stdout.write(' A null is a result, not a failure to act. This gate never fails the build.\n');
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function _isCliEntry(): boolean {
|
|
225
|
+
if (process.argv[1] === undefined) return false;
|
|
226
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
227
|
+
if (import.meta.url === argvUrl) return true;
|
|
228
|
+
try {
|
|
229
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
230
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
231
|
+
return here === argv;
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
238
|
+
process.exitCode = main();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { QUIET, SYMPTOM_DIR, main };
|
|
@@ -46,7 +46,9 @@ import * as os from 'node:os';
|
|
|
46
46
|
import * as path from 'node:path';
|
|
47
47
|
import { fileURLToPath } from 'node:url';
|
|
48
48
|
|
|
49
|
+
import { projectStoreSlug } from './_lib/cc_transcript.js';
|
|
49
50
|
import {
|
|
51
|
+
isArtifactRead,
|
|
50
52
|
isConsultation,
|
|
51
53
|
isUiWrite,
|
|
52
54
|
type ToolEvent,
|
|
@@ -80,6 +82,24 @@ export interface SessionMeasurement {
|
|
|
80
82
|
consulted: number;
|
|
81
83
|
/** Those turns followed later by opening the review skill. */
|
|
82
84
|
reviewOpenedAfter: number;
|
|
85
|
+
/**
|
|
86
|
+
* Those turns that followed reading a provided design artifact.
|
|
87
|
+
*
|
|
88
|
+
* Shares this function's denominator on purpose — the two rates answer
|
|
89
|
+
* different questions over the SAME population ("did it consult the design
|
|
90
|
+
* surface" vs "did it read the handed-over source"), and measuring them on
|
|
91
|
+
* separate denominators is how two instruments about one behaviour stop
|
|
92
|
+
* being comparable.
|
|
93
|
+
*/
|
|
94
|
+
artifactRead: number;
|
|
95
|
+
/** True when this session read a provided artifact at any point. */
|
|
96
|
+
sawArtifact: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* True when this session's FIRST UI write came after an artifact read —
|
|
99
|
+
* the metric named "artifact read before first UI write". Meaningless
|
|
100
|
+
* unless `sawArtifact`, which is why it is reported over that denominator.
|
|
101
|
+
*/
|
|
102
|
+
readBeforeFirstWrite: boolean;
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
export interface RateReport {
|
|
@@ -95,6 +115,16 @@ export interface RateReport {
|
|
|
95
115
|
uiWriteTurns: number;
|
|
96
116
|
consulted: number;
|
|
97
117
|
reviewOpenedAfter: number;
|
|
118
|
+
/** UI-write turns preceded by reading a provided design artifact. */
|
|
119
|
+
artifactRead: number;
|
|
120
|
+
/**
|
|
121
|
+
* Sessions that read a provided artifact at all — the ONLY honest
|
|
122
|
+
* denominator for the read-before-write rate. A session with no handover
|
|
123
|
+
* cannot fail to read one.
|
|
124
|
+
*/
|
|
125
|
+
handoverSessions: number;
|
|
126
|
+
/** Of those, the ones whose first UI write came after the read. */
|
|
127
|
+
handoverReadFirst: number;
|
|
98
128
|
}
|
|
99
129
|
|
|
100
130
|
/**
|
|
@@ -107,7 +137,7 @@ export interface RateReport {
|
|
|
107
137
|
* reads a clean zero out of a store it never found.
|
|
108
138
|
*/
|
|
109
139
|
export function defaultStore(cwd: string): string {
|
|
110
|
-
return path.join(os.homedir(), '.claude', 'projects', cwd
|
|
140
|
+
return path.join(os.homedir(), '.claude', 'projects', projectStoreSlug(cwd));
|
|
111
141
|
}
|
|
112
142
|
|
|
113
143
|
/**
|
|
@@ -138,10 +168,20 @@ export function toolUseToEvent(part: Record<string, unknown>): ToolEvent | null
|
|
|
138
168
|
* consult for the file it already wrote.
|
|
139
169
|
*/
|
|
140
170
|
export function measureSession(timed: readonly TimedEvent[]): SessionMeasurement {
|
|
141
|
-
const out: SessionMeasurement = {
|
|
171
|
+
const out: SessionMeasurement = {
|
|
172
|
+
uiWriteTurns: 0,
|
|
173
|
+
consulted: 0,
|
|
174
|
+
reviewOpenedAfter: 0,
|
|
175
|
+
artifactRead: 0,
|
|
176
|
+
sawArtifact: false,
|
|
177
|
+
readBeforeFirstWrite: false,
|
|
178
|
+
};
|
|
142
179
|
let consulted = false;
|
|
180
|
+
let artifactRead = false;
|
|
143
181
|
/** turn -> was the session already consulted when that turn first wrote UI */
|
|
144
182
|
const writeTurns = new Map<number, boolean>();
|
|
183
|
+
/** turn -> had an artifact been read when that turn first wrote UI */
|
|
184
|
+
const artifactTurns = new Map<number, boolean>();
|
|
145
185
|
let lastReviewOpenAt = -1;
|
|
146
186
|
const reviewOpenPositions: number[] = [];
|
|
147
187
|
const writePositions: Array<{ position: number; turn: number }> = [];
|
|
@@ -150,10 +190,18 @@ export function measureSession(timed: readonly TimedEvent[]): SessionMeasurement
|
|
|
150
190
|
if (isUiWrite(event)) {
|
|
151
191
|
if (!writeTurns.has(turn)) {
|
|
152
192
|
writeTurns.set(turn, consulted);
|
|
193
|
+
artifactTurns.set(turn, artifactRead);
|
|
153
194
|
writePositions.push({ position, turn });
|
|
154
195
|
}
|
|
155
196
|
return;
|
|
156
197
|
}
|
|
198
|
+
// Evaluated INDEPENDENTLY of the consultation branch, never as an
|
|
199
|
+
// early return before it. The two predicates are not disjoint —
|
|
200
|
+
// `skills/design-review/references/design.html` satisfies both — so a
|
|
201
|
+
// `return` here would remove such an event from the consultation
|
|
202
|
+
// numerator and from the discharge-proxy latch. An earlier revision did
|
|
203
|
+
// exactly that while claiming in its comment to prevent it.
|
|
204
|
+
if (isArtifactRead(event)) artifactRead = true;
|
|
157
205
|
if (isConsultation(event)) {
|
|
158
206
|
consulted = true;
|
|
159
207
|
if (event.file.replace(/\\/g, '/').toLowerCase().includes(`skills/${REVIEW_SURFACE}/`)) {
|
|
@@ -165,6 +213,20 @@ export function measureSession(timed: readonly TimedEvent[]): SessionMeasurement
|
|
|
165
213
|
|
|
166
214
|
out.uiWriteTurns = writeTurns.size;
|
|
167
215
|
for (const wasConsulted of writeTurns.values()) if (wasConsulted) out.consulted += 1;
|
|
216
|
+
for (const wasRead of artifactTurns.values()) if (wasRead) out.artifactRead += 1;
|
|
217
|
+
out.sawArtifact = artifactRead;
|
|
218
|
+
// KNOWN IMPRECISION, accepted: ordering inside ONE assistant message comes
|
|
219
|
+
// from the `tool_use` part index, and parallel tool calls in a single
|
|
220
|
+
// message carry no real order. A read and a write issued together can
|
|
221
|
+
// therefore score either way. Not repaired here — a transcript carries no
|
|
222
|
+
// per-call timestamp, so the fix is a different data source rather than a
|
|
223
|
+
// different loop, and the error is bounded to same-message pairs. It is a
|
|
224
|
+
// third reason the published rate is a ceiling.
|
|
225
|
+
//
|
|
226
|
+
// The FIRST write's outcome — `artifactTurns` is insertion-ordered on the
|
|
227
|
+
// first UI write of each turn, so its first value is that write's state.
|
|
228
|
+
const firstWrite = artifactTurns.values().next();
|
|
229
|
+
out.readBeforeFirstWrite = firstWrite.done !== true && firstWrite.value === true;
|
|
168
230
|
// A single max comparison, not a scan per write: the proxy asks only whether
|
|
169
231
|
// ANY review-open follows, so the last one decides every earlier write.
|
|
170
232
|
void reviewOpenPositions;
|
|
@@ -215,6 +277,9 @@ export function measureStore(store: string, limit: number): RateReport {
|
|
|
215
277
|
uiWriteTurns: 0,
|
|
216
278
|
consulted: 0,
|
|
217
279
|
reviewOpenedAfter: 0,
|
|
280
|
+
artifactRead: 0,
|
|
281
|
+
handoverSessions: 0,
|
|
282
|
+
handoverReadFirst: 0,
|
|
218
283
|
};
|
|
219
284
|
// A store that is not there and a store with nothing in it are different
|
|
220
285
|
// findings, and only one of them is about the agent. Collapsing them into
|
|
@@ -254,6 +319,11 @@ export function measureStore(store: string, limit: number): RateReport {
|
|
|
254
319
|
report.uiWriteTurns += measured.uiWriteTurns;
|
|
255
320
|
report.consulted += measured.consulted;
|
|
256
321
|
report.reviewOpenedAfter += measured.reviewOpenedAfter;
|
|
322
|
+
report.artifactRead += measured.artifactRead;
|
|
323
|
+
if (measured.sawArtifact) {
|
|
324
|
+
report.handoverSessions += 1;
|
|
325
|
+
if (measured.readBeforeFirstWrite) report.handoverReadFirst += 1;
|
|
326
|
+
}
|
|
257
327
|
}
|
|
258
328
|
return report;
|
|
259
329
|
}
|
|
@@ -284,6 +354,62 @@ export function render(report: RateReport, threshold: number): string {
|
|
|
284
354
|
lines.push(
|
|
285
355
|
` discharge PROXY (review opened) ${pct(report.reviewOpenedAfter, report.uiWriteTurns)} (${report.reviewOpenedAfter}/${report.uiWriteTurns})`,
|
|
286
356
|
);
|
|
357
|
+
lines.push(
|
|
358
|
+
` artifact read (all UI turns) ${pct(report.artifactRead, report.uiWriteTurns)} (${report.artifactRead}/${report.uiWriteTurns})`,
|
|
359
|
+
);
|
|
360
|
+
lines.push(
|
|
361
|
+
` READ BEFORE FIRST WRITE ${pct(report.handoverReadFirst, report.handoverSessions)} (${report.handoverReadFirst}/${report.handoverSessions} handover session(s))`,
|
|
362
|
+
);
|
|
363
|
+
lines.push('');
|
|
364
|
+
lines.push(
|
|
365
|
+
' READ BEFORE FIRST WRITE is the rate to quote. Its denominator is',
|
|
366
|
+
);
|
|
367
|
+
lines.push(
|
|
368
|
+
' sessions that read a provided artifact at all — a session with no',
|
|
369
|
+
);
|
|
370
|
+
lines.push(
|
|
371
|
+
' handover cannot fail to read one, so including it would dilute the',
|
|
372
|
+
);
|
|
373
|
+
lines.push(
|
|
374
|
+
' rate toward the size of the estate rather than the behaviour.',
|
|
375
|
+
);
|
|
376
|
+
lines.push(
|
|
377
|
+
' That denominator is narrower than it sounds, twice: "artifact" means',
|
|
378
|
+
);
|
|
379
|
+
lines.push(
|
|
380
|
+
' a path ending design.html or under .claude/design-system/ — a handover',
|
|
381
|
+
);
|
|
382
|
+
lines.push(
|
|
383
|
+
' under any other name is not counted — and a session that read one but',
|
|
384
|
+
);
|
|
385
|
+
lines.push(
|
|
386
|
+
' wrote no UI is excluded, since it had nothing to be late for.',
|
|
387
|
+
);
|
|
388
|
+
lines.push(
|
|
389
|
+
' The line above it shares the consultation denominator and is kept for',
|
|
390
|
+
);
|
|
391
|
+
lines.push(
|
|
392
|
+
' comparability with that rate only.',
|
|
393
|
+
);
|
|
394
|
+
lines.push('');
|
|
395
|
+
lines.push(
|
|
396
|
+
' A CEILING, NOT A MEASUREMENT, for two reasons. A session handed an',
|
|
397
|
+
);
|
|
398
|
+
lines.push(
|
|
399
|
+
' artifact that NEVER reads it is invisible — nothing in a transcript',
|
|
400
|
+
);
|
|
401
|
+
lines.push(
|
|
402
|
+
' marks a handover that was ignored, and that case IS the defect. And a',
|
|
403
|
+
);
|
|
404
|
+
lines.push(
|
|
405
|
+
' search whose path argument happens to be the artifact counts as a read',
|
|
406
|
+
);
|
|
407
|
+
lines.push(
|
|
408
|
+
' although nothing was read. Both errors point the same way: the true',
|
|
409
|
+
);
|
|
410
|
+
lines.push(
|
|
411
|
+
' read-before-write rate is at most what is printed here.',
|
|
412
|
+
);
|
|
287
413
|
lines.push('');
|
|
288
414
|
|
|
289
415
|
if (report.sessionsWithUiWrite < threshold) {
|
|
@@ -52,6 +52,8 @@ import * as os from 'node:os';
|
|
|
52
52
|
import * as path from 'node:path';
|
|
53
53
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
54
54
|
|
|
55
|
+
import { projectStoreSlug } from './_lib/cc_transcript.js';
|
|
56
|
+
|
|
55
57
|
const _HERE = fileURLToPath(import.meta.url);
|
|
56
58
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
57
59
|
|
|
@@ -114,11 +116,11 @@ export interface UsageReport {
|
|
|
114
116
|
|
|
115
117
|
/** Default store for the current working directory, mangled the way the host does. */
|
|
116
118
|
export function defaultStore(cwd: string): string {
|
|
117
|
-
//
|
|
118
|
-
// `<repo>/.claude/worktrees/x` lands in `…-agent-config--claude-worktrees-x`.
|
|
119
|
-
//
|
|
119
|
+
// Every non-alphanumeric is flattened, not only separators: a worktree at
|
|
120
|
+
// `<repo>/.claude/worktrees/x+y` lands in `…-agent-config--claude-worktrees-x-y`.
|
|
121
|
+
// A narrower class returns a directory that does not exist, and the caller
|
|
120
122
|
// then reads a clean zero out of a store it never found.
|
|
121
|
-
return path.join(os.homedir(), '.claude', 'projects', cwd
|
|
123
|
+
return path.join(os.homedir(), '.claude', 'projects', projectStoreSlug(cwd));
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
export function measureUsage(store: string, limit: number): UsageReport {
|
|
@@ -59,6 +59,7 @@ import * as path from 'node:path';
|
|
|
59
59
|
import process from 'node:process';
|
|
60
60
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
61
61
|
|
|
62
|
+
import { projectStoreSlug } from './_lib/cc_transcript.js';
|
|
62
63
|
import { entryText, isSidechain, toolUses as _toolUses } from './_lib/transcript_entry.js';
|
|
63
64
|
import { censusSkills, DETERMINISTIC_RE, SKILLS_ROOT } from './report_skill_activation.js';
|
|
64
65
|
|
|
@@ -408,7 +409,7 @@ export interface Sk2Report {
|
|
|
408
409
|
}
|
|
409
410
|
|
|
410
411
|
export function defaultStore(cwd: string): string {
|
|
411
|
-
return path.join(os.homedir(), '.claude', 'projects', cwd
|
|
412
|
+
return path.join(os.homedir(), '.claude', 'projects', projectStoreSlug(cwd));
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
export function scanStore(repoRoot: string, store: string, limit: number): Sk2Report {
|
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
* JSON files (json.dumps indent=2 + trailing newline). No behaviour
|
|
11
11
|
* changes.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* SKILLS_ROOT diverges from the mirrored .py source: it resolves the live
|
|
14
|
+
* skills tree through the shared resolver instead of replicating the retired
|
|
15
|
+
* container literal. Replicating it made every subcommand fail in
|
|
16
|
+
* `_skill_dir()` before doing any work — the twin was byte-faithful to a
|
|
17
|
+
* source that no longer exists and runnable against nothing. Documented per
|
|
18
|
+
* ADR-200 § 6 in `docs/migration/divergences/src-scripts-run_skill_evals.md`;
|
|
19
|
+
* an undocumented difference would be a regression by definition.
|
|
17
20
|
*
|
|
18
21
|
* Sub-agent SPAWNING is per-environment (Claude Code, Augment Code,
|
|
19
22
|
* council) and is left as a stub `_spawn_subagent(...)` that authors
|
|
@@ -25,11 +28,13 @@ import * as fs from 'node:fs';
|
|
|
25
28
|
import * as path from 'node:path';
|
|
26
29
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
30
|
|
|
31
|
+
import { SRC_SKILLS } from './_lib/agent_src.js';
|
|
32
|
+
|
|
28
33
|
const _HERE = fileURLToPath(import.meta.url);
|
|
29
34
|
// src/scripts/run_skill_evals.ts → parents[2] is the repo root (mirrors
|
|
30
35
|
// `Path(__file__).resolve().parent.parent.parent` in the .py).
|
|
31
36
|
export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
32
|
-
export const SKILLS_ROOT =
|
|
37
|
+
export const SKILLS_ROOT = SRC_SKILLS();
|
|
33
38
|
|
|
34
39
|
/** JSON value type for eval specs / grades. */
|
|
35
40
|
type JsonValue =
|
|
@@ -44,6 +44,14 @@
|
|
|
44
44
|
"type": "string",
|
|
45
45
|
"enum": ["package", "project"],
|
|
46
46
|
"default": "package"
|
|
47
|
+
},
|
|
48
|
+
"sources": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"items": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
|
|
53
|
+
},
|
|
54
|
+
"description": "Optional harvest_id list (provenance/harvests.jsonl) this persona may cite as external doctrine. Three distinct states, and the difference is the point: ABSENT = unscoped, today's behaviour for every persona that does not opt in; [] = explicitly declares it asserts only from its own reasoning, nothing harvested; a non-empty list = scoped to exactly those ids. Every declared id must resolve to a ledger row — checked by lint_harvest_provenance.ts, which treats a persona entry as a citation exactly like an inline harvest marker. Not to be confused with the singular `source` field above, which records package-vs-project origin."
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
57
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "provenance-harvest.schema.json",
|
|
4
|
+
"title": "Provenance ledger harvest record",
|
|
5
|
+
"description": "One line of provenance/harvests.jsonl — a single knowledge claim adopted from an external source into this repo's own artefacts. Sibling of provenance-borrow.schema.json, which covers borrowed CODE; this one covers borrowed IDEAS. Contract: provenance/README.md. Enforced by src/scripts/lint_harvest_provenance.ts (hand-rolled validator, following the provenance-borrow precedent — this file documents the contract; it is not loaded at runtime).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["harvest_id", "stated_in", "source_ref", "evidence_locator", "harvested_at", "verdict"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"harvest_id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
|
|
13
|
+
"description": "Kebab-case slug naming the harvested mechanism. This is the id an artefact cites; it must be unique across the ledger."
|
|
14
|
+
},
|
|
15
|
+
"stated_in": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Repo-relative path of the artefact asserting the claim, optionally suffixed ':<line>'. The path must exist."
|
|
18
|
+
},
|
|
19
|
+
"source_ref": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Either a public source pinned to a revision ('<url>@<sha>'), or an opaque token ('opaque:<id>' / 'ENC1:<payload>') when source-confidentiality keeps the source name out of the tracked tree. A bare URL with no revision is rejected — an unpinned reference cannot be re-verified."
|
|
22
|
+
},
|
|
23
|
+
"evidence_locator": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"minLength": 3,
|
|
26
|
+
"description": "Where inside the source the claim was read — a file:line, a section anchor, or an equivalent locator. Free-form because sources differ, but never empty: a claim whose evidence cannot be located again is not a citation."
|
|
27
|
+
},
|
|
28
|
+
"harvested_at": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
31
|
+
"description": "ISO-8601 date (YYYY-MM-DD) the harvest landed."
|
|
32
|
+
},
|
|
33
|
+
"verdict": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"enum": ["adopt", "adapt"],
|
|
36
|
+
"description": "How the mechanism entered this repo. Only adopt and adapt produce a row — a rejected, already-present, or unclear finding is recorded in the analysis document, never in the ledger, because nothing in the tree cites it."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -148,6 +148,36 @@ export function classify(rel: string): TriageClass {
|
|
|
148
148
|
return 'C';
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Non-gate scripts eligible for the Class-A advisory pass.
|
|
153
|
+
*
|
|
154
|
+
* WHY A SECOND POPULATION EXISTS AT ALL. The gate population above is
|
|
155
|
+
* deliberately shared with the ratchet and the registration test, because three
|
|
156
|
+
* sites once carried their own regex and disagreed. That sharing is worth
|
|
157
|
+
* keeping — but it means the sweep's reach is a FILENAME PREFIX, and a script
|
|
158
|
+
* reading a retired container is dead whether or not its name starts with one
|
|
159
|
+
* of five words. Measured: `run_skill_evals.ts` roots its `SKILLS_ROOT` at the
|
|
160
|
+
* skills subtree of the container ADR-051 retired (see `CLASS_A_RETIRED`), and
|
|
161
|
+
* every subcommand fails in `_skill_dir()` before doing any work — while its
|
|
162
|
+
* sibling `skill_trigger_eval.ts`, same retired root and a `skill_` prefix, is
|
|
163
|
+
* reported. One visible, one not, on the prefix alone.
|
|
164
|
+
*
|
|
165
|
+
* So this pass widens the READING, never the shared population: it is
|
|
166
|
+
* Class-A-only (a closed set of retired containers, needing no allowlist to stay
|
|
167
|
+
* honest), it is advisory, and it feeds neither the ratchet count nor the exit
|
|
168
|
+
* code. Widening `matchesGatePattern` instead would move the ratchet base for a
|
|
169
|
+
* reason unrelated to any gate.
|
|
170
|
+
*/
|
|
171
|
+
export function isNonGateScript(filename: string): boolean {
|
|
172
|
+
if (!filename.endsWith('.ts')) return false;
|
|
173
|
+
if (filename.endsWith('.d.ts') || filename.endsWith('.test.ts')) return false;
|
|
174
|
+
// This file declares the retired containers as a classification table
|
|
175
|
+
// (`CLASS_A_RETIRED`); reading its own constant back as a dead root is a
|
|
176
|
+
// self-report, not a finding.
|
|
177
|
+
if (filename === 'sweep_dead_scan_roots.ts') return false;
|
|
178
|
+
return !isGateFile(filename);
|
|
179
|
+
}
|
|
180
|
+
|
|
151
181
|
export interface Finding {
|
|
152
182
|
rel: string;
|
|
153
183
|
names: string;
|
|
@@ -827,6 +857,20 @@ export function main(argv: string[]): number {
|
|
|
827
857
|
if (r.confirmed.length || r.unproven.length) results.set(f, r);
|
|
828
858
|
}
|
|
829
859
|
|
|
860
|
+
// Class-A advisory over the scripts the shared gate population cannot reach.
|
|
861
|
+
// Reuses the same extractor, so a prose mention or a report string is not a
|
|
862
|
+
// finding — only a literal join over a recognised root base is.
|
|
863
|
+
const advisory: string[] = [];
|
|
864
|
+
const advisoryPop = fs.existsSync(args.scripts)
|
|
865
|
+
? fs.readdirSync(args.scripts).filter(isNonGateScript).sort()
|
|
866
|
+
: [];
|
|
867
|
+
for (const f of advisoryPop) {
|
|
868
|
+
const r = analyze(fs.readFileSync(path.join(args.scripts, f), 'utf-8'), repoRoot);
|
|
869
|
+
for (const h of [...r.confirmed, ...r.unproven]) {
|
|
870
|
+
if (classify(h.rel) === 'A') advisory.push(` advisory [A] ${f}: ${h.rel} (${h.names})`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
830
874
|
const used = new Set<Disposition>();
|
|
831
875
|
const disposed = (script: string, rel: string): Disposition | undefined => {
|
|
832
876
|
const hit = ledger.find((l) => l.script === script && l.rel === rel);
|
|
@@ -899,6 +943,13 @@ export function main(argv: string[]): number {
|
|
|
899
943
|
if (!args.quiet) {
|
|
900
944
|
process.stdout.write(`sweep_dead_scan_roots: ${pop.length} gate script(s) scanned\n`);
|
|
901
945
|
for (const l of lines) process.stdout.write(`${l}\n`);
|
|
946
|
+
if (advisory.length) {
|
|
947
|
+
process.stdout.write(
|
|
948
|
+
`sweep_dead_scan_roots: ${advisoryPop.length} non-gate script(s) scanned ` +
|
|
949
|
+
'(Class-A advisory — not counted, not gated)\n',
|
|
950
|
+
);
|
|
951
|
+
for (const l of advisory) process.stdout.write(`${l}\n`);
|
|
952
|
+
}
|
|
902
953
|
}
|
|
903
954
|
|
|
904
955
|
if (stale.length) {
|
package/src/server/io/yamlIO.ts
CHANGED
|
@@ -144,8 +144,6 @@ export const TEMPLATE_PLACEHOLDER_DEFAULTS: Readonly<Record<string, string>> = {
|
|
|
144
144
|
__DISCIPLINE_PROFILE__: 'auto',
|
|
145
145
|
__USER_TYPE__: '',
|
|
146
146
|
__CHAT_HISTORY_FREQUENCY__: 'per_turn',
|
|
147
|
-
__CHAT_HISTORY_MAX_SIZE_KB__: '2048',
|
|
148
|
-
__CHAT_HISTORY_ON_OVERFLOW__: 'rotate',
|
|
149
147
|
};
|
|
150
148
|
|
|
151
149
|
/** Replace every known template placeholder with its default value. */
|