@event4u/agent-config 16.1.0 → 16.2.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +237 -248
- package/dist/agent-src/commands/roadmap/process-full.md +1 -1
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
- package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_conformance.js +902 -782
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +10 -10
- 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 +6666 -2581
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/install.mjs +848 -733
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2747 -1510
- package/dist/server/schemas/settings.js +23 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +6 -4
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +29 -0
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +2 -2
- package/src/config/gate-coverage.yml +234 -3
- package/src/config/gate-violation-baselines.json +7 -3
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +2 -2
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/scripts/_cli/cmd_doctor.ts +5 -19
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +245 -3
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/compile_hook_manifest.ts +60 -16
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +42 -5
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +8 -5
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/install.ts +3 -4
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +55 -0
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +1 -1
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -2
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +31 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
package/src/scripts/ci_settle.ts
CHANGED
|
@@ -237,9 +237,30 @@ export function classifyPoll(stdout: string, stderr: string, status: number | nu
|
|
|
237
237
|
return { kind: 'pending', total: rows.length, done };
|
|
238
238
|
}
|
|
239
239
|
const bad = new Set(['FAILURE', 'TIMED_OUT', 'CANCELLED', 'ACTION_REQUIRED', 'STARTUP_FAILURE']);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
// A concurrency group that supersedes its own earlier run leaves TWO rows
|
|
241
|
+
// under one name: the superseded one CANCELLED, the one that counts
|
|
242
|
+
// SUCCESS. The forge reports that check green; this classifier reported the
|
|
243
|
+
// whole PR red, because CANCELLED sat in `bad` and nothing looked at the
|
|
244
|
+
// twin. Measured 2026-10-01 on PR #2135 (`lint commit subjects`), where the
|
|
245
|
+
// false red cost a run a diagnosis it did not owe.
|
|
246
|
+
//
|
|
247
|
+
// The reprieve is deliberately the narrowest one that fixes it: CANCELLED
|
|
248
|
+
// only, and only when a row of the SAME NAME concluded SUCCESS. A genuine
|
|
249
|
+
// cancellation — a human stopping a run, a timeout killing one — has no
|
|
250
|
+
// successful twin and stays failing. Widening this to every bad conclusion,
|
|
251
|
+
// or to CANCELLED unconditionally, would trade a false red for a false
|
|
252
|
+
// green, which is the worse error in a tool whose output is acted on.
|
|
253
|
+
const nameOf = (r: (typeof rows)[number]): string => String(r.name ?? '?');
|
|
254
|
+
const concl = (r: (typeof rows)[number]): string => String(r.conclusion).toUpperCase();
|
|
255
|
+
const succeeded = new Set(rows.filter((r) => concl(r) === 'SUCCESS').map(nameOf));
|
|
256
|
+
const failing = [
|
|
257
|
+
...new Set(
|
|
258
|
+
rows
|
|
259
|
+
.filter((r) => bad.has(concl(r)))
|
|
260
|
+
.filter((r) => !(concl(r) === 'CANCELLED' && succeeded.has(nameOf(r))))
|
|
261
|
+
.map(nameOf),
|
|
262
|
+
),
|
|
263
|
+
];
|
|
243
264
|
return { kind: 'settled', failing, total: rows.length };
|
|
244
265
|
}
|
|
245
266
|
|
|
@@ -293,25 +314,131 @@ function sleepSync(seconds: number): void {
|
|
|
293
314
|
});
|
|
294
315
|
}
|
|
295
316
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
317
|
+
/** The only flags this waiter honours. Anything else is a caller mistake. */
|
|
318
|
+
const KNOWN_FLAGS = ['--timeout-min', '--interval-sec'] as const;
|
|
319
|
+
|
|
320
|
+
const USAGE =
|
|
321
|
+
'usage: ci_settle <pr> [--timeout-min N] [--interval-sec N]\n' +
|
|
322
|
+
` default --timeout-min is ${String(FOREGROUND_CEILING_MIN)}, which fits inside one foreground Bash call (600 s cap).\n` +
|
|
323
|
+
' a longer wait is a BACKGROUND job, not a bigger number — a foreground call past the cap is killed and reports nothing.\n';
|
|
324
|
+
|
|
325
|
+
export type ParsedArgs =
|
|
326
|
+
| { kind: 'ok'; pr: string; timeoutMin: number; intervalSec: number }
|
|
327
|
+
| { kind: 'usage'; message: string };
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Parse argv, REFUSING anything this waiter would not honour.
|
|
331
|
+
*
|
|
332
|
+
* Refusal rather than a default is the whole point. The previous parser read
|
|
333
|
+
* its two knobs with `argv.indexOf(flag)` and filtered positionals on a leading
|
|
334
|
+
* `--`, so a caller who wrote the near-universal `--timeout 1700` got two
|
|
335
|
+
* silent substitutions at once: the unknown flag was ignored, and its value —
|
|
336
|
+
* carrying no dashes — survived as a second positional and was dropped. The
|
|
337
|
+
* wait then ran for the 9-minute default while the caller believed they had
|
|
338
|
+
* asked for 28, and the resulting `DID NOT SETTLE` read as a slow CI rather
|
|
339
|
+
* than as their own typo.
|
|
340
|
+
*
|
|
341
|
+
* That is the one failure a waiter must not have. Its entire product is a
|
|
342
|
+
* verdict somebody will act on, and a verdict produced under arguments the
|
|
343
|
+
* caller did not actually give is worse than no verdict, because it looks like
|
|
344
|
+
* one. So every unrecognised flag, every unusable value and every extra
|
|
345
|
+
* positional is a usage refusal, exit 2 — the code this tool already reserves
|
|
346
|
+
* for "no verdict is claimed".
|
|
347
|
+
*
|
|
348
|
+
* `--flag=value` is accepted beside `--flag value` because it is the other
|
|
349
|
+
* spelling a caller reaches for, and refusing it would trade one silent
|
|
350
|
+
* surprise for a noisy one with no safety gained.
|
|
351
|
+
*/
|
|
352
|
+
export function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
353
|
+
const refuse = (why: string): ParsedArgs => ({ kind: 'usage', message: `ci_settle: ${why}\n${USAGE}` });
|
|
354
|
+
|
|
355
|
+
const positional: string[] = [];
|
|
356
|
+
const given = new Map<string, string>();
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < argv.length; i++) {
|
|
359
|
+
const a = argv[i] as string;
|
|
360
|
+
if (!a.startsWith('--')) {
|
|
361
|
+
positional.push(a);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const eq = a.indexOf('=');
|
|
365
|
+
const name = eq === -1 ? a : a.slice(0, eq);
|
|
366
|
+
if (!(KNOWN_FLAGS as readonly string[]).includes(name)) {
|
|
367
|
+
return refuse(
|
|
368
|
+
`unknown flag ${name}. This waiter spells its deadline --timeout-min (in MINUTES), not --timeout`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (given.has(name)) return refuse(`${name} given twice`);
|
|
372
|
+
if (eq !== -1) {
|
|
373
|
+
given.set(name, a.slice(eq + 1));
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const v = argv[i + 1];
|
|
377
|
+
if (v === undefined || v.startsWith('--')) return refuse(`${name} needs a value`);
|
|
378
|
+
given.set(name, v);
|
|
379
|
+
i++;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const raw = positional[0];
|
|
383
|
+
if (raw === undefined) return refuse('no PR number given');
|
|
384
|
+
if (positional.length > 1) {
|
|
385
|
+
return refuse(
|
|
386
|
+
`unexpected argument ${JSON.stringify(positional[1])} — this tool takes exactly one positional, the PR number; ` +
|
|
387
|
+
'a flag value must follow its own flag',
|
|
304
388
|
);
|
|
305
|
-
return 2;
|
|
306
389
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
390
|
+
|
|
391
|
+
// Finding 1 of the 2026-10-01 R2 review: the first version of this guard
|
|
392
|
+
// validated every OPTIONAL argument and waved the mandatory one through, so
|
|
393
|
+
// the failure it exists to remove stayed reachable through the front door.
|
|
394
|
+
// `ci_settle PR-2130` parsed as ok; `gh pr view PR-2130` then exits non-zero
|
|
395
|
+
// on every poll, each one classifies as unreadable, no streak caps the loop,
|
|
396
|
+
// and the run spends its entire budget to arrive at `DID NOT SETTLE` — which
|
|
397
|
+
// is read as a slow CI rather than as the typo it is. A guard that refuses
|
|
398
|
+
// the arguments a caller may omit and accepts the one they cannot is not a
|
|
399
|
+
// guard. `#123` and a PR URL are admitted because both are what a caller
|
|
400
|
+
// copies out of the forge; a branch name is NOT, because this tool's whole
|
|
401
|
+
// verdict is bound to one head and a branch makes that binding ambiguous.
|
|
402
|
+
const pr = /^#?[0-9]+$/.test(raw)
|
|
403
|
+
? raw.replace(/^#/, '')
|
|
404
|
+
: (/^https?:\/\/[^\s]*\/pull\/([0-9]+)(?:[/?#].*)?$/.exec(raw)?.[1] ?? null);
|
|
405
|
+
if (pr === null || pr === '0' || /^0+$/.test(pr)) {
|
|
406
|
+
return refuse(
|
|
407
|
+
`${JSON.stringify(raw)} is not a PR number — give the number itself (2130), #2130, or the pull-request URL`,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const num = (flag: string, dflt: number): number | string => {
|
|
412
|
+
const v = given.get(flag);
|
|
413
|
+
if (v === undefined) return dflt;
|
|
414
|
+
const unit = flag === '--timeout-min' ? 'minutes' : 'seconds';
|
|
415
|
+
if (!/^[0-9]+$/.test(v)) return `${flag} wants a whole number of ${unit}, got ${JSON.stringify(v)}`;
|
|
416
|
+
const n = parseInt(v, 10);
|
|
417
|
+
// Finding 2: the parser this replaced guarded with `Number.isFinite`, and
|
|
418
|
+
// the digits-only test that replaced it does not. A long enough run of
|
|
419
|
+
// digits parses to Infinity, clears `n > 0`, and makes `deadline`
|
|
420
|
+
// unreachable — an unbounded wait in a tool whose contract is that it
|
|
421
|
+
// always returns a stated verdict or a stated non-verdict.
|
|
422
|
+
if (!Number.isFinite(n)) return `${flag} is too large to be a number of ${unit}`;
|
|
423
|
+
if (n <= 0) return `${flag} wants a positive number, got ${v}`;
|
|
424
|
+
return n;
|
|
313
425
|
};
|
|
426
|
+
|
|
314
427
|
const timeoutMin = num('--timeout-min', FOREGROUND_CEILING_MIN);
|
|
428
|
+
if (typeof timeoutMin === 'string') return refuse(timeoutMin);
|
|
429
|
+
const intervalSec = num('--interval-sec', 60);
|
|
430
|
+
if (typeof intervalSec === 'string') return refuse(intervalSec);
|
|
431
|
+
|
|
432
|
+
return { kind: 'ok', pr, timeoutMin, intervalSec };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function main(argv: readonly string[]): number {
|
|
436
|
+
const parsed = parseArgs(argv);
|
|
437
|
+
if (parsed.kind === 'usage') {
|
|
438
|
+
process.stderr.write(parsed.message);
|
|
439
|
+
return 2;
|
|
440
|
+
}
|
|
441
|
+
const { pr, timeoutMin, intervalSec } = parsed;
|
|
315
442
|
if (timeoutMin > FOREGROUND_CEILING_MIN) {
|
|
316
443
|
// Said once, up front, rather than discovered when the call is killed:
|
|
317
444
|
// a truncated wait produces no line at all, so the warning has to come
|
|
@@ -322,7 +449,6 @@ export function main(argv: readonly string[]): number {
|
|
|
322
449
|
'the deadline and will report nothing — run it as a background job for a wait this long.\n',
|
|
323
450
|
);
|
|
324
451
|
}
|
|
325
|
-
const intervalSec = num('--interval-sec', 60);
|
|
326
452
|
|
|
327
453
|
// Up front, because waiting nine minutes to learn the PR was merged before
|
|
328
454
|
// the wait began is the expensive way to find out.
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* `unpicked-alternative` "either X or Y", "option A … option B"
|
|
20
20
|
* `unchecked-assumption` "assuming", "presumably", "probably fine"
|
|
21
21
|
* `missing-verify` an open step with no `verify:` line
|
|
22
|
+
* `unfalsifiable-verify` a step WITH a `verify:` line whose oracle
|
|
23
|
+
* cannot say no
|
|
22
24
|
* `ambiguous-acceptance` a vague acceptance criterion (improve / clean up
|
|
23
25
|
* / best practices / handle errors properly)
|
|
24
26
|
* `contradictory` "contradicts", "conflicts with", "but also"
|
|
@@ -35,6 +37,24 @@
|
|
|
35
37
|
* Exit codes: 0 no open decisions · 1 open decisions found · 2 bad argv.
|
|
36
38
|
* Finding open decisions is the POINT, so `--strict` is what makes exit 1 a
|
|
37
39
|
* failure for a caller that wants one; the default is a report.
|
|
40
|
+
*
|
|
41
|
+
* ONE FAMILY IS A LISTING AND NEVER A GATE. `unfalsifiable-verify` exits 0
|
|
42
|
+
* unconditionally — under `--strict` too — and that is a property of the
|
|
43
|
+
* family, not a flag a caller can flip. The reason is the one this script's
|
|
44
|
+
* own header already records about a detector that fires on ordinary prose,
|
|
45
|
+
* sharpened by a measurement: on 2026-09-29 the active roadmap tree carried
|
|
46
|
+
* 230 clauses and 6 machine-decidable expectations — 97.4% of them state no
|
|
47
|
+
* oracle — so a family promoted to a gate would red all but a handful of
|
|
48
|
+
* roadmaps at once, and the cheapest repair for a gate that reds on
|
|
49
|
+
* everything is to weaken it until it finds nothing. The exemption is
|
|
50
|
+
* narrow: a `tbd` in the same corpus still goes red under `--strict`.
|
|
51
|
+
*
|
|
52
|
+
* The figure is the MEASURED one, not the commissioning roadmap's. That
|
|
53
|
+
* roadmap asserted zero expectations tree-wide; a reading refuted it — six
|
|
54
|
+
* exist, all six written before the grammar was legal, and three of them are
|
|
55
|
+
* that roadmap's own verify lines. The producing
|
|
56
|
+
* command is `./scripts-run src/scripts/roadmap_verify_share`, so the number
|
|
57
|
+
* above is re-derivable rather than remembered.
|
|
38
58
|
*/
|
|
39
59
|
|
|
40
60
|
import * as fs from 'node:fs';
|
|
@@ -42,6 +62,8 @@ import * as path from 'node:path';
|
|
|
42
62
|
import process from 'node:process';
|
|
43
63
|
import { fileURLToPath } from 'node:url';
|
|
44
64
|
|
|
65
|
+
import { parseVerifyClause, type VerifyClause } from './_lib/verify_clause.js';
|
|
66
|
+
|
|
45
67
|
const _HERE = fileURLToPath(import.meta.url);
|
|
46
68
|
const _PROG = 'closure_scan';
|
|
47
69
|
|
|
@@ -50,6 +72,7 @@ export type Kind =
|
|
|
50
72
|
| 'unpicked-alternative'
|
|
51
73
|
| 'unchecked-assumption'
|
|
52
74
|
| 'missing-verify'
|
|
75
|
+
| 'unfalsifiable-verify'
|
|
53
76
|
| 'ambiguous-acceptance'
|
|
54
77
|
| 'contradictory'
|
|
55
78
|
| 'product-semantics'
|
|
@@ -74,6 +97,7 @@ export const OWNERSHIP_BY_KIND: Readonly<Record<Kind, string>> = {
|
|
|
74
97
|
'unpicked-alternative': 'contested-technical',
|
|
75
98
|
'unchecked-assumption': 'reversible-technical',
|
|
76
99
|
'missing-verify': 'deterministic',
|
|
100
|
+
'unfalsifiable-verify': 'deterministic',
|
|
77
101
|
'ambiguous-acceptance': 'reversible-technical',
|
|
78
102
|
contradictory: 'contested-technical',
|
|
79
103
|
'product-semantics': 'product-owned',
|
|
@@ -88,6 +112,73 @@ export const OWNER_ROUTED: ReadonlySet<string> = new Set([
|
|
|
88
112
|
'destructive-owned',
|
|
89
113
|
]);
|
|
90
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Families that report and never gate — `--strict` cannot make these exit 1.
|
|
117
|
+
*
|
|
118
|
+
* A set rather than a flag, so the exemption is a property of the family that a
|
|
119
|
+
* caller cannot accidentally promote. See the header for the measurement that
|
|
120
|
+
* makes promotion a bad trade today.
|
|
121
|
+
*/
|
|
122
|
+
export const NON_BLOCKING_KINDS: ReadonlySet<Kind> = new Set<Kind>(['unfalsifiable-verify']);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Command heads whose exit status is decided by something other than the
|
|
126
|
+
* property a step is claiming.
|
|
127
|
+
*
|
|
128
|
+
* Deliberately short. `echo`, `true` and `printf` cannot fail at all; `cat`,
|
|
129
|
+
* `ls`, `pwd` and `date` fail only on a missing path or a broken clock, never
|
|
130
|
+
* on the CONTENT a step asserts. Anything whose exit code already tracks the
|
|
131
|
+
* claim — `grep` (1 on no match), a linter, a test runner — is absent on
|
|
132
|
+
* purpose: including it would turn this listing into a census of every verify
|
|
133
|
+
* line in the tree, which is step 2.2's instrument, not this one.
|
|
134
|
+
*/
|
|
135
|
+
const FIXED_OUTPUT_HEADS: ReadonlySet<string> = new Set(['echo', 'true', 'printf', 'cat', 'ls', 'pwd', 'date']);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Why this clause's oracle cannot say no — or `null` when it can.
|
|
139
|
+
*
|
|
140
|
+
* Three shapes, each one a way of carrying a `verify:` line that reads as
|
|
141
|
+
* verification and decides nothing.
|
|
142
|
+
*
|
|
143
|
+
* THE COUNT IS A LOWER BOUND, and saying so is the point. `scan()` assigns at
|
|
144
|
+
* most one kind per unit and the line patterns run first, so a step that also
|
|
145
|
+
* says "assuming", "merge" or "deploy" is claimed by the broader family and
|
|
146
|
+
* never reaches this one. Measured on 2026-09-29: 12 of 50 candidates were
|
|
147
|
+
* shadowed that way, so the published listing under-reports by roughly a
|
|
148
|
+
* quarter. A reader told a number and not told it is a floor would read the
|
|
149
|
+
* remainder as clean, which is the same mistake as reporting a share off a
|
|
150
|
+
* corpus nobody scanned.
|
|
151
|
+
*/
|
|
152
|
+
export function unfalsifiableReason(clause: VerifyClause): string | null {
|
|
153
|
+
if (clause.command === null) {
|
|
154
|
+
// A MANUAL clause asserting a QUANTITY nothing produced. Narrowed to a
|
|
155
|
+
// multi-digit number or an explicit `N of`/`N%` shape: a lone digit is
|
|
156
|
+
// usually a step id or an exit code being discussed, and firing on
|
|
157
|
+
// those would bury the real ones.
|
|
158
|
+
const quantity = /\b\d{2,}\b/.test(clause.raw) || /\b\d+\s*(?:%|of\b)/.test(clause.raw);
|
|
159
|
+
return quantity ? 'a manual clause asserts a quantity nothing here produces' : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const head = (clause.command.trim().split(/\s+/)[0] ?? '').split('/').pop() ?? '';
|
|
163
|
+
if (FIXED_OUTPUT_HEADS.has(head) && clause.expect?.kind !== 'regex') {
|
|
164
|
+
// A regex expectation rescues a fixed-output head: `cat f -> /x/` does
|
|
165
|
+
// decide something. An exit expectation does not — naming exit 0 on a
|
|
166
|
+
// command that cannot exit anything else adds a symbol, not an oracle.
|
|
167
|
+
return `the command head \`${head}\` produces a fixed result, so the clause cannot fail`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (clause.expect?.kind === 'regex') {
|
|
171
|
+
try {
|
|
172
|
+
if (new RegExp(clause.expect.source).test(clause.command)) {
|
|
173
|
+
return 'the expectation matches the command text, so an echo of the command satisfies it';
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
// An expectation that cannot compile never became one upstream.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
91
182
|
interface Pattern {
|
|
92
183
|
readonly kind: Kind;
|
|
93
184
|
readonly re: RegExp;
|
|
@@ -145,6 +236,17 @@ export interface Unit {
|
|
|
145
236
|
/** 1-based line of the unit's first line. */
|
|
146
237
|
readonly line: number;
|
|
147
238
|
readonly text: string;
|
|
239
|
+
/**
|
|
240
|
+
* The same block with its NEWLINES intact.
|
|
241
|
+
*
|
|
242
|
+
* `text` is joined with spaces so a wrapped pattern still matches across
|
|
243
|
+
* the wrap, and that is right for the line patterns. It is wrong for the
|
|
244
|
+
* `verify:` clause: with no newline left, "to end of line" means "to end of
|
|
245
|
+
* the step", so a manual clause inherited every digit in the evidence block
|
|
246
|
+
* written under it. Measured before this field existed: 46 of 50
|
|
247
|
+
* `unfalsifiable-verify` hits fired on a quantity the clause never wrote.
|
|
248
|
+
*/
|
|
249
|
+
readonly blockText: string;
|
|
148
250
|
readonly inAcceptance: boolean;
|
|
149
251
|
readonly isOpenStep: boolean;
|
|
150
252
|
readonly hasVerify: boolean;
|
|
@@ -188,6 +290,7 @@ export function units(lines: readonly string[]): Unit[] {
|
|
|
188
290
|
// written with single spaces stops matching across the wrap —
|
|
189
291
|
// which is the case this unit exists to catch.
|
|
190
292
|
text: block.join(' ').replace(/\s+/g, ' '),
|
|
293
|
+
blockText: block.join('\n'),
|
|
191
294
|
inAcceptance,
|
|
192
295
|
isOpenStep: OPEN_STEP_RE.test(line),
|
|
193
296
|
hasVerify: /(?:^|\s)verify:/m.test(block.join('\n')),
|
|
@@ -198,6 +301,7 @@ export function units(lines: readonly string[]): Unit[] {
|
|
|
198
301
|
out.push({
|
|
199
302
|
line: i + 1,
|
|
200
303
|
text: line,
|
|
304
|
+
blockText: line,
|
|
201
305
|
inAcceptance,
|
|
202
306
|
isOpenStep: false,
|
|
203
307
|
hasVerify: false,
|
|
@@ -227,6 +331,14 @@ export function scan(text: string): Finding[] {
|
|
|
227
331
|
if (matched === null && u.isOpenStep && !u.hasVerify && !u.inAcceptance) {
|
|
228
332
|
matched = 'missing-verify';
|
|
229
333
|
}
|
|
334
|
+
// A step that HAS a verify line, whose oracle still cannot say no.
|
|
335
|
+
// Checked on closed steps too — a `[x]` flipped on a command that
|
|
336
|
+
// could not fail is the defect in its completed form, and restricting
|
|
337
|
+
// this to open steps would hide every instance that already landed.
|
|
338
|
+
if (matched === null && u.hasVerify && !u.inAcceptance) {
|
|
339
|
+
const clause = parseVerifyClause(u.blockText);
|
|
340
|
+
if (clause !== null && unfalsifiableReason(clause) !== null) matched = 'unfalsifiable-verify';
|
|
341
|
+
}
|
|
230
342
|
if (matched === null) continue;
|
|
231
343
|
findings.push({
|
|
232
344
|
id: '',
|
|
@@ -316,7 +428,9 @@ export function main(argv?: readonly string[]): number {
|
|
|
316
428
|
}
|
|
317
429
|
}
|
|
318
430
|
if (!findings.length) return 0;
|
|
319
|
-
|
|
431
|
+
// `--strict` goes red on the families that gate, never on the listing.
|
|
432
|
+
const blocking = findings.filter((f) => !NON_BLOCKING_KINDS.has(f.kind));
|
|
433
|
+
return strict && blocking.length > 0 ? 1 : 0;
|
|
320
434
|
}
|
|
321
435
|
|
|
322
436
|
function _isCliEntry(): boolean {
|
|
@@ -44,23 +44,66 @@ import * as path from 'node:path';
|
|
|
44
44
|
import { fileURLToPath } from 'node:url';
|
|
45
45
|
import { parse as parseYaml } from 'yaml';
|
|
46
46
|
|
|
47
|
-
import {
|
|
47
|
+
import { tableFingerprint } from './hooks/table_fingerprint.js';
|
|
48
48
|
|
|
49
49
|
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
50
|
-
const YAML_PATH = path.join(REPO_ROOT, 'src', 'scripts', 'hook_manifest.yaml');
|
|
51
|
-
const JSON_PATH = path.join(REPO_ROOT, 'src', 'scripts', 'hook_manifest.json');
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The plumbing tables that carry a compiled sibling, keyed by `--table`.
|
|
53
|
+
*
|
|
54
|
+
* `key` is the wrapper field the reader looks under, and it differs per table
|
|
55
|
+
* because each reader already had one: `dispatch_hook._load_yaml` reads
|
|
56
|
+
* `manifest`, `host_lowering.loadHostLowering` reads `table`. Renaming either
|
|
57
|
+
* would be a plumbing change for cosmetics, so the writer follows the readers.
|
|
58
|
+
*
|
|
59
|
+
* `yamlVersion` is 1.1 for the manifest and default for the lowering table,
|
|
60
|
+
* again because that is what each READER passes. A compiler that parsed with
|
|
61
|
+
* different settings than the fallback path would produce a fast path that
|
|
62
|
+
* disagrees with its own slow path on `yes`/`no`/`on`/`off` scalars — a
|
|
63
|
+
* divergence no fingerprint can catch, because both files would be current.
|
|
64
|
+
*/
|
|
65
|
+
const TABLES = {
|
|
66
|
+
manifest: {
|
|
67
|
+
yaml: path.join(REPO_ROOT, 'src', 'scripts', 'hook_manifest.yaml'),
|
|
68
|
+
json: path.join(REPO_ROOT, 'src', 'scripts', 'hook_manifest.json'),
|
|
69
|
+
key: 'manifest',
|
|
70
|
+
yamlVersion: '1.1' as const,
|
|
71
|
+
},
|
|
72
|
+
'host-lowering': {
|
|
73
|
+
yaml: path.join(REPO_ROOT, 'src', 'scripts', 'hooks', 'host_lowering.yaml'),
|
|
74
|
+
json: path.join(REPO_ROOT, 'src', 'scripts', 'hooks', 'host_lowering.json'),
|
|
75
|
+
key: 'table',
|
|
76
|
+
yamlVersion: undefined,
|
|
77
|
+
},
|
|
78
|
+
} as const;
|
|
79
|
+
|
|
80
|
+
export type TableName = keyof typeof TABLES;
|
|
81
|
+
|
|
82
|
+
export function compile(yamlText: string, table: TableName = 'manifest'): string {
|
|
83
|
+
const spec = TABLES[table];
|
|
84
|
+
const doc = (spec.yamlVersion === undefined
|
|
85
|
+
? parseYaml(yamlText)
|
|
86
|
+
: parseYaml(yamlText, { version: spec.yamlVersion })) as unknown;
|
|
87
|
+
return JSON.stringify({ fingerprint: tableFingerprint(yamlText), [spec.key]: doc });
|
|
58
88
|
}
|
|
59
89
|
|
|
60
90
|
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
61
91
|
let outPath: string | null = null;
|
|
92
|
+
let table: TableName = 'manifest';
|
|
62
93
|
for (let i = 0; i < argv.length; i += 1) {
|
|
63
94
|
const a = argv[i] as string;
|
|
95
|
+
if (a === '--table') {
|
|
96
|
+
const next = argv[i + 1];
|
|
97
|
+
if (next === undefined || !(next in TABLES)) {
|
|
98
|
+
process.stderr.write(
|
|
99
|
+
`compile_hook_manifest: --table needs one of ${Object.keys(TABLES).join(', ')}\n`,
|
|
100
|
+
);
|
|
101
|
+
return 2;
|
|
102
|
+
}
|
|
103
|
+
table = next as TableName;
|
|
104
|
+
i += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
64
107
|
if (a === '--out') {
|
|
65
108
|
const next = argv[i + 1];
|
|
66
109
|
if (next === undefined || next.startsWith('--')) {
|
|
@@ -73,13 +116,14 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
73
116
|
}
|
|
74
117
|
process.stderr.write(
|
|
75
118
|
`compile_hook_manifest: unknown argument ${JSON.stringify(a)}\n` +
|
|
76
|
-
|
|
119
|
+
`usage: compile_hook_manifest [--table ${Object.keys(TABLES).join('|')}] [--out <path>]\n`,
|
|
77
120
|
);
|
|
78
121
|
return 2;
|
|
79
122
|
}
|
|
80
123
|
|
|
81
|
-
const
|
|
82
|
-
const
|
|
124
|
+
const spec = TABLES[table];
|
|
125
|
+
const text = fs.readFileSync(spec.yaml, 'utf-8');
|
|
126
|
+
const next = compile(text, table);
|
|
83
127
|
|
|
84
128
|
if (outPath !== null) {
|
|
85
129
|
// Comparison copy. No `already current` short-circuit: the caller asked
|
|
@@ -89,15 +133,15 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
89
133
|
return 0;
|
|
90
134
|
}
|
|
91
135
|
|
|
92
|
-
const current = fs.existsSync(
|
|
136
|
+
const current = fs.existsSync(spec.json) ? fs.readFileSync(spec.json, 'utf-8') : null;
|
|
93
137
|
if (current === next) {
|
|
94
|
-
process.stdout.write(
|
|
138
|
+
process.stdout.write(`compile_hook_manifest: ${table} already current\n`);
|
|
95
139
|
return 0;
|
|
96
140
|
}
|
|
97
|
-
fs.writeFileSync(
|
|
141
|
+
fs.writeFileSync(spec.json, next);
|
|
98
142
|
process.stdout.write(
|
|
99
|
-
`compile_hook_manifest: wrote ${path.relative(REPO_ROOT,
|
|
100
|
-
`(${String(next.length)} bytes, fingerprint ${
|
|
143
|
+
`compile_hook_manifest: wrote ${path.relative(REPO_ROOT, spec.json)} ` +
|
|
144
|
+
`(${String(next.length)} bytes, fingerprint ${tableFingerprint(text)})\n`,
|
|
101
145
|
);
|
|
102
146
|
return 0;
|
|
103
147
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_README": "Denylist for check_no_external_sources.
|
|
2
|
+
"_README": "Denylist for check_no_external_sources.ts — blocks readable inspiration/harvest/comparison source names from re-entering the tracked tree. See rule source-confidentiality + the 2026-06-13 source-confidentiality sweep. Recommending an integrated tool is allowed; recording that we copied/derived/were-inspired-by an external source is not. To intentionally retain a source link, encrypt it via src/scripts/_lib/link_crypto.ts. A COMPARISON CORPUS IS A PRODUCER OF THIS FILE: every subject a comparison / scorecard round audits owes at least one `deny` pattern here, and the `subjects` registry at the bottom of this file is that binding made machine-checkable — tests/scripts/external_source_subject_registry.test.ts reds when a registry row is not matched by any pattern above. Adding a subject without a row is the defect that produced the 2026-09-29 reading recorded in that test. (Both filenames above read `.py` until 2026-09-29; the Python originals were retired in the ADR-200 teardown, so the pointers named files that no longer exist.)",
|
|
3
3
|
"deny": [
|
|
4
4
|
"kdcllc",
|
|
5
5
|
"nidhinjs",
|
|
@@ -65,7 +65,17 @@
|
|
|
65
65
|
"book-to-skill",
|
|
66
66
|
"project-codeguard",
|
|
67
67
|
"\\bcodeguard\\b",
|
|
68
|
-
"\\bcosai\\b"
|
|
68
|
+
"\\bcosai\\b",
|
|
69
|
+
"\\bqoderai\\b",
|
|
70
|
+
"\\bbetter-harness\\b",
|
|
71
|
+
"\\bshanraisshan\\b",
|
|
72
|
+
"claude-code-best-practice",
|
|
73
|
+
"\\boraios\\b",
|
|
74
|
+
"oraios/serena",
|
|
75
|
+
"\\bopen-gsd\\b",
|
|
76
|
+
"\\bgsd-core\\b",
|
|
77
|
+
"\\bgsd-build\\b",
|
|
78
|
+
"\\bget-shit-done\\b"
|
|
69
79
|
],
|
|
70
80
|
"_deny_digests_README": "DORMANT (road-to-source-silence Phase 1.1). HMAC-SHA256 digests of the deny set, derived from the gitignored private master by src/scripts/build_source_digests.ts. It is EMPTY on purpose: the plaintext `deny` array above stays in force and is not deleted. Provisioning the CI secret SOURCE_DENY_KEY, generating production digests, removing the plaintext array and switching CI to strict mode are ONE ATOMIC MAINTAINER CHANGE (AI council 2026-08-28, blocker `where-the-key-lives`, 2/2) — splitting them leaves the gate either failing every run for want of a key or silently degraded to warn mode, which is the failure this whole programme exists to remove. Recipe: docs/maintainers/source-deny-digests.md",
|
|
71
81
|
"deny_digests": [],
|
|
@@ -103,6 +113,21 @@
|
|
|
103
113
|
},
|
|
104
114
|
"_excluded_tokens": {
|
|
105
115
|
"agent-os": "collides with our own coined 'Agent-OS primitive' term (universal-skills) — too FP-prone to auto-deny; scrub external 'agent-os' mentions by review.",
|
|
106
|
-
"telegraph-shrink / telegraph-condense": "collide with our own 'telegraph'/'telegraph-condensed' feature vocabulary; the telegraph upstream is guarded via 'caveman' + 'juliusbrussee' instead."
|
|
107
|
-
|
|
116
|
+
"telegraph-shrink / telegraph-condense": "collide with our own 'telegraph'/'telegraph-condensed' feature vocabulary; the telegraph upstream is guarded via 'caveman' + 'juliusbrussee' instead.",
|
|
117
|
+
"serena": "S8's repo half. A common given name — a bare \\bserena\\b would turn a confidentiality gate into a ban on a person's name in any example or persona. The subject is guarded by the owner token \\boraios\\b plus the owner-qualified 'oraios/serena' instead, which is exactly the standard the pre-existing 'obra/superpowers' row already meets. Measured 2026-09-29: 0 occurrences in 11,598 tracked files, so the exclusion is a policy choice about future prose, not a reaction to a live collision.",
|
|
118
|
+
"gsd": "S9/S10's shared stem. A three-letter acronym is too collision-prone to deny bare. The two subjects are guarded by the four qualified rows \\bopen-gsd\\b, \\bgsd-core\\b, \\bgsd-build\\b and \\bget-shit-done\\b instead. Note the last one is hyphenated on purpose: the spaced English phrase is ordinary prose and is deliberately NOT denied."
|
|
119
|
+
},
|
|
120
|
+
"_subjects_README": "Registry of the comparison / scorecard subject set, added 2026-09-29 by road-to-a-denylist-that-sees-every-subject. It exists so that 'a subject was audited' and 'a subject is denied' cannot drift apart again: tests/scripts/external_source_subject_registry.test.ts asserts every `slug` below is matched by at least one `deny` pattern above, and reds when a row is added without its pattern. The slugs live HERE and nowhere else in the tracked tree — this file is the one path skip_paths exempts from its own scan, and it already had to hold these tokens as deny data, so the registry discloses nothing the array above did not. A fixture that named them would be a tracked file publishing exactly what the gate exists to hide, so the test synthesises its corpus from this registry at run time and never commits a resolved name. `id` values are the S1..S10 identifiers the maintainer-held source map uses; they are deliberately opaque here.",
|
|
121
|
+
"subjects": [
|
|
122
|
+
{ "id": "S1", "slug": "graphify-labs/graphify" },
|
|
123
|
+
{ "id": "S2", "slug": "ruvnet/ruflo" },
|
|
124
|
+
{ "id": "S3", "slug": "affaan-m/everything-claude-code" },
|
|
125
|
+
{ "id": "S4", "slug": "obra/superpowers" },
|
|
126
|
+
{ "id": "S5", "slug": "qoderai/better-harness", "note": "Added 2026-09-29. Owner and repo both denied; the repo half is a hyphenated compound, so the ordinary word 'harness' — which this tree uses constantly — is untouched." },
|
|
127
|
+
{ "id": "S6", "slug": "github/spec-kit" },
|
|
128
|
+
{ "id": "S7", "slug": "shanraisshan/claude-code-best-practice", "note": "Added 2026-09-29. The repo half is denied unanchored because the hyphenated triple is distinctive on its own; the spaced phrase 'claude code best practice' is ordinary prose and stays allowed." },
|
|
129
|
+
{ "id": "S8", "slug": "oraios/serena", "note": "Added 2026-09-29. Owner-qualified on purpose — see _excluded_tokens.serena." },
|
|
130
|
+
{ "id": "S9", "slug": "open-gsd/gsd-core", "note": "Added 2026-09-29. Both halves denied with \\b anchors; the bare stem is excluded — see _excluded_tokens.gsd." },
|
|
131
|
+
{ "id": "S10", "slug": "gsd-build/get-shit-done", "note": "Added 2026-09-29. Archived predecessor of S9. Not re-audited, but a name is a name: it is denied on the same terms." }
|
|
132
|
+
]
|
|
108
133
|
}
|