instar 1.3.489 → 1.3.491

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.
Files changed (175) hide show
  1. package/dashboard/index.html +30 -0
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +1124 -28
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  6. package/dist/config/ConfigDefaults.js +23 -0
  7. package/dist/config/ConfigDefaults.js.map +1 -1
  8. package/dist/core/CodexResumeMap.d.ts +95 -0
  9. package/dist/core/CodexResumeMap.d.ts.map +1 -0
  10. package/dist/core/CodexResumeMap.js +283 -0
  11. package/dist/core/CodexResumeMap.js.map +1 -0
  12. package/dist/core/GuardPostureStore.d.ts +38 -0
  13. package/dist/core/GuardPostureStore.d.ts.map +1 -0
  14. package/dist/core/GuardPostureStore.js +87 -0
  15. package/dist/core/GuardPostureStore.js.map +1 -0
  16. package/dist/core/MachinePoolRegistry.d.ts +16 -0
  17. package/dist/core/MachinePoolRegistry.d.ts.map +1 -1
  18. package/dist/core/MachinePoolRegistry.js +24 -1
  19. package/dist/core/MachinePoolRegistry.js.map +1 -1
  20. package/dist/core/MeshRpc.d.ts +3 -0
  21. package/dist/core/MeshRpc.d.ts.map +1 -1
  22. package/dist/core/MeshRpc.js +5 -0
  23. package/dist/core/MeshRpc.js.map +1 -1
  24. package/dist/core/ModelSwapService.d.ts +26 -11
  25. package/dist/core/ModelSwapService.d.ts.map +1 -1
  26. package/dist/core/ModelSwapService.js +59 -21
  27. package/dist/core/ModelSwapService.js.map +1 -1
  28. package/dist/core/PeerPresencePuller.d.ts +9 -0
  29. package/dist/core/PeerPresencePuller.d.ts.map +1 -1
  30. package/dist/core/PeerPresencePuller.js +1 -1
  31. package/dist/core/PeerPresencePuller.js.map +1 -1
  32. package/dist/core/PostUpdateMigrator.d.ts +16 -0
  33. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  34. package/dist/core/PostUpdateMigrator.js +92 -4
  35. package/dist/core/PostUpdateMigrator.js.map +1 -1
  36. package/dist/core/SessionManager.d.ts +4 -0
  37. package/dist/core/SessionManager.d.ts.map +1 -1
  38. package/dist/core/SessionManager.js +3 -0
  39. package/dist/core/SessionManager.js.map +1 -1
  40. package/dist/core/SessionRefresh.d.ts +36 -7
  41. package/dist/core/SessionRefresh.d.ts.map +1 -1
  42. package/dist/core/SessionRefresh.js +90 -29
  43. package/dist/core/SessionRefresh.js.map +1 -1
  44. package/dist/core/TopicProfileOrchestrator.d.ts +480 -0
  45. package/dist/core/TopicProfileOrchestrator.d.ts.map +1 -0
  46. package/dist/core/TopicProfileOrchestrator.js +1404 -0
  47. package/dist/core/TopicProfileOrchestrator.js.map +1 -0
  48. package/dist/core/TopicProfileResolver.d.ts +104 -0
  49. package/dist/core/TopicProfileResolver.d.ts.map +1 -0
  50. package/dist/core/TopicProfileResolver.js +231 -0
  51. package/dist/core/TopicProfileResolver.js.map +1 -0
  52. package/dist/core/TopicProfileStore.d.ts +308 -0
  53. package/dist/core/TopicProfileStore.d.ts.map +1 -0
  54. package/dist/core/TopicProfileStore.js +781 -0
  55. package/dist/core/TopicProfileStore.js.map +1 -0
  56. package/dist/core/TopicProfileTransferCarrier.d.ts +227 -0
  57. package/dist/core/TopicProfileTransferCarrier.d.ts.map +1 -0
  58. package/dist/core/TopicProfileTransferCarrier.js +533 -0
  59. package/dist/core/TopicProfileTransferCarrier.js.map +1 -0
  60. package/dist/core/TopicResumeMap.d.ts +67 -1
  61. package/dist/core/TopicResumeMap.d.ts.map +1 -1
  62. package/dist/core/TopicResumeMap.js +114 -1
  63. package/dist/core/TopicResumeMap.js.map +1 -1
  64. package/dist/core/classifyProfileChange.d.ts +83 -0
  65. package/dist/core/classifyProfileChange.d.ts.map +1 -0
  66. package/dist/core/classifyProfileChange.js +274 -0
  67. package/dist/core/classifyProfileChange.js.map +1 -0
  68. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  69. package/dist/core/devGatedFeatures.js +6 -0
  70. package/dist/core/devGatedFeatures.js.map +1 -1
  71. package/dist/core/frameworkSessionLaunch.d.ts +26 -0
  72. package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
  73. package/dist/core/frameworkSessionLaunch.js +58 -0
  74. package/dist/core/frameworkSessionLaunch.js.map +1 -1
  75. package/dist/core/slackRefreshBinding.d.ts +83 -0
  76. package/dist/core/slackRefreshBinding.d.ts.map +1 -0
  77. package/dist/core/slackRefreshBinding.js +54 -0
  78. package/dist/core/slackRefreshBinding.js.map +1 -0
  79. package/dist/core/topicProfileIngress.d.ts +128 -0
  80. package/dist/core/topicProfileIngress.d.ts.map +1 -0
  81. package/dist/core/topicProfileIngress.js +249 -0
  82. package/dist/core/topicProfileIngress.js.map +1 -0
  83. package/dist/core/topicProfileValidation.d.ts +109 -0
  84. package/dist/core/topicProfileValidation.d.ts.map +1 -0
  85. package/dist/core/topicProfileValidation.js +247 -0
  86. package/dist/core/topicProfileValidation.js.map +1 -0
  87. package/dist/core/topicProfileWriteSurface.d.ts +222 -0
  88. package/dist/core/topicProfileWriteSurface.d.ts.map +1 -0
  89. package/dist/core/topicProfileWriteSurface.js +631 -0
  90. package/dist/core/topicProfileWriteSurface.js.map +1 -0
  91. package/dist/core/types.d.ts +66 -0
  92. package/dist/core/types.d.ts.map +1 -1
  93. package/dist/core/types.js.map +1 -1
  94. package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
  95. package/dist/lifeline/TelegramLifeline.js +6 -0
  96. package/dist/lifeline/TelegramLifeline.js.map +1 -1
  97. package/dist/messaging/TelegramAdapter.d.ts +10 -1
  98. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  99. package/dist/messaging/TelegramAdapter.js +41 -1
  100. package/dist/messaging/TelegramAdapter.js.map +1 -1
  101. package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts +8 -0
  102. package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts.map +1 -1
  103. package/dist/monitoring/ActiveWorkSilenceSentinel.js +8 -0
  104. package/dist/monitoring/ActiveWorkSilenceSentinel.js.map +1 -1
  105. package/dist/monitoring/ContextWedgeSentinel.d.ts +8 -0
  106. package/dist/monitoring/ContextWedgeSentinel.d.ts.map +1 -1
  107. package/dist/monitoring/ContextWedgeSentinel.js +8 -0
  108. package/dist/monitoring/ContextWedgeSentinel.js.map +1 -1
  109. package/dist/monitoring/GuardPostureTripwire.d.ts +3 -27
  110. package/dist/monitoring/GuardPostureTripwire.d.ts.map +1 -1
  111. package/dist/monitoring/GuardPostureTripwire.js +8 -76
  112. package/dist/monitoring/GuardPostureTripwire.js.map +1 -1
  113. package/dist/monitoring/GuardRegistry.d.ts +48 -0
  114. package/dist/monitoring/GuardRegistry.d.ts.map +1 -0
  115. package/dist/monitoring/GuardRegistry.js +49 -0
  116. package/dist/monitoring/GuardRegistry.js.map +1 -0
  117. package/dist/monitoring/SessionReaper.d.ts +7 -0
  118. package/dist/monitoring/SessionReaper.d.ts.map +1 -1
  119. package/dist/monitoring/SessionReaper.js +9 -0
  120. package/dist/monitoring/SessionReaper.js.map +1 -1
  121. package/dist/monitoring/SessionWatchdog.d.ts +8 -0
  122. package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
  123. package/dist/monitoring/SessionWatchdog.js +8 -0
  124. package/dist/monitoring/SessionWatchdog.js.map +1 -1
  125. package/dist/monitoring/SocketDisconnectSentinel.d.ts +8 -0
  126. package/dist/monitoring/SocketDisconnectSentinel.d.ts.map +1 -1
  127. package/dist/monitoring/SocketDisconnectSentinel.js +8 -0
  128. package/dist/monitoring/SocketDisconnectSentinel.js.map +1 -1
  129. package/dist/monitoring/guardManifest.d.ts +67 -0
  130. package/dist/monitoring/guardManifest.d.ts.map +1 -0
  131. package/dist/monitoring/guardManifest.js +536 -0
  132. package/dist/monitoring/guardManifest.js.map +1 -0
  133. package/dist/monitoring/guardPosture.d.ts +77 -0
  134. package/dist/monitoring/guardPosture.d.ts.map +1 -0
  135. package/dist/monitoring/guardPosture.js +198 -0
  136. package/dist/monitoring/guardPosture.js.map +1 -0
  137. package/dist/monitoring/guardPostureView.d.ts +100 -0
  138. package/dist/monitoring/guardPostureView.d.ts.map +1 -0
  139. package/dist/monitoring/guardPostureView.js +294 -0
  140. package/dist/monitoring/guardPostureView.js.map +1 -0
  141. package/dist/monitoring/probes/GuardPostureProbe.d.ts +82 -0
  142. package/dist/monitoring/probes/GuardPostureProbe.d.ts.map +1 -0
  143. package/dist/monitoring/probes/GuardPostureProbe.js +384 -0
  144. package/dist/monitoring/probes/GuardPostureProbe.js.map +1 -0
  145. package/dist/scaffold/templates.d.ts.map +1 -1
  146. package/dist/scaffold/templates.js +18 -0
  147. package/dist/scaffold/templates.js.map +1 -1
  148. package/dist/scheduler/JobScheduler.d.ts +10 -0
  149. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  150. package/dist/scheduler/JobScheduler.js +12 -0
  151. package/dist/scheduler/JobScheduler.js.map +1 -1
  152. package/dist/server/AgentServer.d.ts +40 -0
  153. package/dist/server/AgentServer.d.ts.map +1 -1
  154. package/dist/server/AgentServer.js +54 -3
  155. package/dist/server/AgentServer.js.map +1 -1
  156. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  157. package/dist/server/CapabilityIndex.js +12 -0
  158. package/dist/server/CapabilityIndex.js.map +1 -1
  159. package/dist/server/peerUrlGuard.d.ts +35 -0
  160. package/dist/server/peerUrlGuard.d.ts.map +1 -0
  161. package/dist/server/peerUrlGuard.js +93 -0
  162. package/dist/server/peerUrlGuard.js.map +1 -0
  163. package/dist/server/routes.d.ts +31 -0
  164. package/dist/server/routes.d.ts.map +1 -1
  165. package/dist/server/routes.js +407 -1
  166. package/dist/server/routes.js.map +1 -1
  167. package/package.json +3 -2
  168. package/scripts/lint-guard-manifest.js +264 -0
  169. package/src/data/builtin-manifest.json +67 -67
  170. package/src/data/state-coherence-registry.json +54 -18
  171. package/src/scaffold/templates.ts +18 -0
  172. package/upgrades/1.3.490.md +34 -0
  173. package/upgrades/1.3.491.md +37 -0
  174. package/upgrades/side-effects/guard-posture-endpoint.md +101 -0
  175. package/upgrades/side-effects/topic-profile.md +142 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.489",
3
+ "version": "1.3.491",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,9 +28,10 @@
28
28
  "test:contract": "node scripts/run-contract-tests.js",
29
29
  "test:contract:raw": "vitest run --config vitest.contract.config.ts",
30
30
  "test:all": "vitest run && vitest run --config vitest.integration.config.ts && vitest run --config vitest.e2e.config.ts",
31
- "lint": "tsc --noEmit && node scripts/lint-no-direct-destructive.js && node scripts/lint-no-direct-llm-http.js && node scripts/lint-no-direct-url-log.js && node scripts/lint-no-unfunneled-topic-creation.js && node scripts/lint-no-unfunneled-headless-launch.js && node scripts/lint-state-registry.js && node scripts/lint-cas-emit-placement.js && node scripts/lint-journal-actuation-ban.js && node scripts/lint-no-blocking-process-scans.js && node scripts/lint-dev-agent-dark-gate.js && node scripts/lint-llm-attribution.js && node scripts/check-codex-rule1-drift.js",
31
+ "lint": "tsc --noEmit && node scripts/lint-no-direct-destructive.js && node scripts/lint-no-direct-llm-http.js && node scripts/lint-no-direct-url-log.js && node scripts/lint-no-unfunneled-topic-creation.js && node scripts/lint-no-unfunneled-headless-launch.js && node scripts/lint-state-registry.js && node scripts/lint-cas-emit-placement.js && node scripts/lint-journal-actuation-ban.js && node scripts/lint-no-blocking-process-scans.js && node scripts/lint-dev-agent-dark-gate.js && node scripts/lint-guard-manifest.js && node scripts/lint-llm-attribution.js && node scripts/check-codex-rule1-drift.js",
32
32
  "lint:destructive": "node scripts/lint-no-direct-destructive.js",
33
33
  "lint:dev-agent-dark-gate": "node scripts/lint-dev-agent-dark-gate.js",
34
+ "lint:guard-manifest": "node scripts/lint-guard-manifest.js",
34
35
  "lint:dev-agent-dark-gate:staged": "node scripts/lint-dev-agent-dark-gate.js --staged",
35
36
  "lint:destructive:staged": "node scripts/lint-no-direct-destructive.js --staged",
36
37
  "lint:llm-http": "node scripts/lint-no-direct-llm-http.js",
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lint-guard-manifest.js — the guard-inventory CI ratchet
4
+ * (GUARD-POSTURE-ENDPOINT-SPEC §2.1, "CI ratchet (manifest-driven, repo-wide)").
5
+ *
6
+ * THE RULE: every guard-shaped component in src/monitoring, src/messaging,
7
+ * src/lifeline, src/core must be CLASSIFIED — it appears either as a
8
+ * `component:` in GUARD_MANIFEST (it IS an inventory guard) or in NOT_A_GUARD
9
+ * (it deliberately is not, with a real reason). A future guard cannot be
10
+ * forgotten from the /guards inventory: the moment its file lands, this lint
11
+ * fails until someone makes the classification decision explicitly
12
+ * (Structure > Willpower — same contract as lint-dev-agent-dark-gate.js +
13
+ * DARK_GATE_EXCLUSIONS).
14
+ *
15
+ * Three assertions:
16
+ *
17
+ * A. EVERY CANDIDATE CLASSIFIED — a candidate component is (1) a .ts file in
18
+ * one of the four dirs whose basename matches the guard-shape suffix
19
+ * pattern (Sentinel|Reaper|Watchdog|Guard|Tripwire|Monitor|Beacon|
20
+ * Detector|Promoter|Pauser|Backstop), component name = basename, OR
21
+ * (2) a name in ADDITIONAL_CANDIDATES (enabled-gated boot constructs
22
+ * whose filenames don't match the pattern, e.g. QuotaTracker, PromptGate).
23
+ * Each candidate must appear in GUARD_MANIFEST's `component:` fields or
24
+ * in NOT_A_GUARD.
25
+ *
26
+ * B. REAL REASONS — every NOT_A_GUARD entry's reason must be ≥12
27
+ * non-whitespace chars (defeats placeholder reasons; same bar as
28
+ * DARK_GATE_EXCLUSIONS).
29
+ *
30
+ * C. EXACTLY ONE LIST — a component must not appear in BOTH GUARD_MANIFEST
31
+ * and NOT_A_GUARD (a dual classification is a contradiction the
32
+ * inventory cannot resolve).
33
+ *
34
+ * LIMITATIONS (P2 Signal-vs-Authority — do NOT claim full closure):
35
+ * - Detection is the file-basename suffix pattern + the explicit
36
+ * ADDITIONAL_CANDIDATES list. A guard-shaped component in a file whose
37
+ * name matches neither (e.g. a guard class defined inside server.ts)
38
+ * evades detection until someone names it in ADDITIONAL_CANDIDATES.
39
+ * Deliberate: cheap, deterministic, explainable — the precedent's
40
+ * pragmatism over an AST-grade analyzer.
41
+ * - The manifest is parsed STATICALLY (regex over the source — this lint
42
+ * must not import TS). `component:` and `reason:` must be single-line
43
+ * 'single-' or "double-quoted" string literals, which is the file's
44
+ * enforced house style; a template-literal or multi-line reason would be
45
+ * invisible to the parse (and the entry would then fail assertion A,
46
+ * loudly, not silently pass).
47
+ *
48
+ * Exit codes:
49
+ * 0 — no violations.
50
+ * 1 — at least one violation.
51
+ *
52
+ * Usage:
53
+ * node scripts/lint-guard-manifest.js # full scan of the four dirs
54
+ * node scripts/lint-guard-manifest.js path1 path2 # specific candidate files
55
+ *
56
+ * Test override:
57
+ * INSTAR_GUARDLINT_MANIFEST=<path> # point the parse at a fixture manifest
58
+ */
59
+
60
+ import fs from 'node:fs';
61
+ import path from 'node:path';
62
+ import { fileURLToPath } from 'node:url';
63
+
64
+ const __filename = fileURLToPath(import.meta.url);
65
+ const __dirname = path.dirname(__filename);
66
+ const ROOT = path.resolve(__dirname, '..');
67
+
68
+ const MANIFEST_REL = 'src/monitoring/guardManifest.ts';
69
+ const MANIFEST_ABS = process.env.INSTAR_GUARDLINT_MANIFEST
70
+ ? path.resolve(process.env.INSTAR_GUARDLINT_MANIFEST)
71
+ : path.join(ROOT, MANIFEST_REL);
72
+
73
+ // The repo-wide scan boundary (spec §2.1: "in ANY of src/monitoring,
74
+ // src/messaging, src/lifeline, src/core" — path-pattern linting scoped to one
75
+ // directory would miss the spec's own canonical examples).
76
+ const GUARD_DIRS = ['src/monitoring', 'src/messaging', 'src/lifeline', 'src/core'];
77
+
78
+ // Guard-shape detection 1: the basename suffix pattern.
79
+ const GUARD_SHAPE_BASENAME =
80
+ /(Sentinel|Reaper|Watchdog|Guard|Tripwire|Monitor|Beacon|Detector|Promoter|Pauser|Backstop)\.ts$/;
81
+
82
+ // Guard-shape detection 2: explicit additional candidates — enabled-gated
83
+ // boot-constructed components whose FILENAMES do not match the suffix pattern.
84
+ // Append here when a new guard-shaped component ships under a non-matching
85
+ // name (the lint then forces its classification like any other candidate).
86
+ const ADDITIONAL_CANDIDATES = [
87
+ 'QuotaTracker', // monitoring.quotaTracking
88
+ 'ResourceLedger', // monitoring.resourceLedger.enabled
89
+ 'TelemetryCollector', // monitoring.telemetry.enabled
90
+ 'StallTriageNurse', // monitoring.triage.enabled
91
+ 'TriageOrchestrator', // monitoring.triageOrchestrator.enabled
92
+ 'GrowthMilestoneAnalyst',// monitoring.growthAnalyst.enabled (dev-gated)
93
+ 'BlockerLedger', // monitoring.blockerLedger.enabled (dev-gated)
94
+ 'SecretSync', // multiMachine.secretSync.enabled
95
+ 'CoherenceJournal', // multiMachine.coherenceJournal.enabled
96
+ 'PromptGate', // monitoring.promptGate.enabled
97
+ ];
98
+
99
+ const MIN_REASON_NON_WS = 12;
100
+
101
+ /** Strip line + block comments so commented-out entries can't satisfy the rule. */
102
+ function stripComments(src) {
103
+ return src
104
+ .replace(/\/\*[\s\S]*?\*\//g, '')
105
+ .replace(/^\s*\/\/.*$/gm, '');
106
+ }
107
+
108
+ /** Extract the source slice of `export const NAME ... = [ ... ] as const;`. */
109
+ function extractArrayBlock(src, name) {
110
+ const declRe = new RegExp(`export\\s+const\\s+${name}\\b[\\s\\S]*?=\\s*\\[`);
111
+ const m = declRe.exec(src);
112
+ if (!m) return null;
113
+ const start = m.index + m[0].length;
114
+ const end = src.indexOf('] as const', start);
115
+ if (end < 0) return null;
116
+ return src.slice(start, end);
117
+ }
118
+
119
+ /**
120
+ * Statically parse the manifest file (regex over source — never imports TS).
121
+ * Returns { manifestComponents: Set, notAGuard: [{component, reason, line}] }.
122
+ */
123
+ function parseManifest(manifestAbs) {
124
+ const raw = fs.readFileSync(manifestAbs, 'utf-8');
125
+ const src = stripComments(raw);
126
+
127
+ const manifestBlock = extractArrayBlock(src, 'GUARD_MANIFEST');
128
+ const notAGuardBlock = extractArrayBlock(src, 'NOT_A_GUARD');
129
+ if (manifestBlock === null || notAGuardBlock === null) {
130
+ return {
131
+ error: `could not locate ${manifestBlock === null ? 'GUARD_MANIFEST' : 'NOT_A_GUARD'} array in ${manifestAbs}`,
132
+ };
133
+ }
134
+
135
+ const manifestComponents = new Set();
136
+ const componentRe = /component:\s*(['"])([^'"]+)\1/g;
137
+ for (const m of manifestBlock.matchAll(componentRe)) manifestComponents.add(m[2]);
138
+
139
+ // NOT_A_GUARD entries: `{ component: '...', reason: '...' }` (single-line
140
+ // string literals — the file's house style; see header LIMITATIONS).
141
+ const notAGuard = [];
142
+ const entryRe = /component:\s*(['"])([^'"]+)\1\s*,\s*reason:\s*(['"])((?:[^\\]|\\.)*?)\3/g;
143
+ for (const m of notAGuardBlock.matchAll(entryRe)) {
144
+ notAGuard.push({ component: m[2], reason: m[4] });
145
+ }
146
+
147
+ return { manifestComponents, notAGuard };
148
+ }
149
+
150
+ /** Walk the four guard dirs for candidate .ts files (skip tests/declarations). */
151
+ function listCandidateFiles() {
152
+ const out = [];
153
+ for (const rel of GUARD_DIRS) {
154
+ const dir = path.join(ROOT, rel);
155
+ if (!fs.existsSync(dir)) continue;
156
+ const walk = (d) => {
157
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
158
+ const full = path.join(d, e.name);
159
+ if (e.isDirectory()) {
160
+ if (e.name === 'node_modules') continue;
161
+ walk(full);
162
+ } else if (
163
+ e.name.endsWith('.ts') &&
164
+ !e.name.endsWith('.test.ts') &&
165
+ !e.name.endsWith('.d.ts')
166
+ ) {
167
+ out.push(full);
168
+ }
169
+ }
170
+ };
171
+ walk(dir);
172
+ }
173
+ return out;
174
+ }
175
+
176
+ function resolveCandidateFiles() {
177
+ const explicit = process.argv.slice(2).filter((a) => !a.startsWith('--'));
178
+ if (explicit.length) return explicit.map((f) => path.resolve(ROOT, f));
179
+ return listCandidateFiles();
180
+ }
181
+
182
+ // ── Run ──────────────────────────────────────────────────────────────────────
183
+
184
+ const violations = [];
185
+
186
+ if (!fs.existsSync(MANIFEST_ABS)) {
187
+ console.error(`lint-guard-manifest: manifest not found at ${MANIFEST_ABS}`);
188
+ process.exit(1);
189
+ }
190
+
191
+ const parsed = parseManifest(MANIFEST_ABS);
192
+ if (parsed.error) {
193
+ console.error(`lint-guard-manifest: ${parsed.error}`);
194
+ process.exit(1);
195
+ }
196
+ const { manifestComponents, notAGuard } = parsed;
197
+ const notAGuardComponents = new Set(notAGuard.map((e) => e.component));
198
+
199
+ // ── Assertion B: every NOT_A_GUARD reason is real (≥12 non-ws chars) ──
200
+ for (const e of notAGuard) {
201
+ const reasonLen = (e.reason || '').replace(/\s/g, '').length;
202
+ if (reasonLen < MIN_REASON_NON_WS) {
203
+ violations.push({
204
+ file: MANIFEST_REL, kind: 'B: NOT_A_GUARD reason too short',
205
+ text: `${e.component} → reason '${e.reason}' (${reasonLen} non-ws chars)`,
206
+ fix: `a NOT_A_GUARD reason must be ≥${MIN_REASON_NON_WS} non-whitespace chars (defeats placeholder reasons)`,
207
+ });
208
+ }
209
+ }
210
+
211
+ // ── Assertion C: exactly one list ──
212
+ for (const c of notAGuardComponents) {
213
+ if (manifestComponents.has(c)) {
214
+ violations.push({
215
+ file: MANIFEST_REL, kind: 'C: dual classification',
216
+ text: `${c} appears in BOTH GUARD_MANIFEST and NOT_A_GUARD`,
217
+ fix: 'a component is a guard or it is not — remove it from one list',
218
+ });
219
+ }
220
+ }
221
+
222
+ // ── Assertion A: every candidate component is classified ──
223
+ const explicitArgs = process.argv.slice(2).filter((a) => !a.startsWith('--'));
224
+ const candidates = new Map(); // component name → where it was detected
225
+ for (const file of resolveCandidateFiles()) {
226
+ if (!fs.existsSync(file)) continue;
227
+ const base = path.basename(file);
228
+ if (!GUARD_SHAPE_BASENAME.test(base)) continue;
229
+ candidates.set(base.replace(/\.ts$/, ''), path.relative(ROOT, file));
230
+ }
231
+ // ADDITIONAL_CANDIDATES are repo-global names — only meaningful on a full
232
+ // scan (an explicit-args run, e.g. a test fixture, scopes to those files).
233
+ if (explicitArgs.length === 0) {
234
+ for (const name of ADDITIONAL_CANDIDATES) {
235
+ if (!candidates.has(name)) candidates.set(name, '(ADDITIONAL_CANDIDATES)');
236
+ }
237
+ }
238
+
239
+ for (const [component, where] of [...candidates.entries()].sort()) {
240
+ if (manifestComponents.has(component)) continue;
241
+ if (notAGuardComponents.has(component)) continue;
242
+ violations.push({
243
+ file: where, kind: 'A: unclassified guard-shaped component',
244
+ text: `${component} is in NEITHER GUARD_MANIFEST (component:) NOR NOT_A_GUARD`,
245
+ fix: `declare it in GUARD_MANIFEST (it joins the /guards inventory) OR classify it in NOT_A_GUARD with a ≥${MIN_REASON_NON_WS}-char reason (${MANIFEST_REL})`,
246
+ });
247
+ }
248
+
249
+ if (violations.length === 0) {
250
+ console.log('lint-guard-manifest: clean');
251
+ process.exit(0);
252
+ }
253
+
254
+ console.error('\n❌ lint-guard-manifest found violations of the guard-inventory classification standard:\n');
255
+ console.error('NOTE: detection is the file-basename suffix pattern + ADDITIONAL_CANDIDATES only —');
256
+ console.error('a guard-shaped component under a non-matching filename evades it until named in');
257
+ console.error('ADDITIONAL_CANDIDATES. This closes the forgotten-file hole, not every hole.\n');
258
+ for (const v of violations) {
259
+ console.error(` ${v.file} [${v.kind}]`);
260
+ console.error(` ${v.text}`);
261
+ console.error(` fix: ${v.fix}\n`);
262
+ }
263
+ console.error('Standard: every guard-shaped component is CLASSIFIED — declared in GUARD_MANIFEST (it appears in the /guards inventory) or in NOT_A_GUARD with a real reason. A future guard cannot be silently forgotten. Spec: docs/specs/GUARD-POSTURE-ENDPOINT-SPEC.md §2.1\n');
264
+ process.exit(1);