@tech-leads-club/harness-toolkit 0.5.1 → 0.7.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.
Files changed (44) hide show
  1. package/bin/generate-schema.ts +67 -0
  2. package/bin/tlc-build.mjs +26 -1
  3. package/bin/tlc-cli.ts +43 -11
  4. package/config.example.json +1 -0
  5. package/dist/compact-before.mjs +83 -83
  6. package/dist/doctor.mjs +79 -79
  7. package/dist/init-project.mjs +87 -87
  8. package/dist/install-runtime.mjs +82 -82
  9. package/dist/lessons-cli.mjs +86 -86
  10. package/dist/obs-cli.mjs +79 -79
  11. package/dist/prompt-submit.mjs +83 -83
  12. package/dist/refresh-model-prices.mjs +82 -82
  13. package/dist/response-after.mjs +83 -83
  14. package/dist/run.mjs +83 -83
  15. package/dist/session-end.mjs +89 -89
  16. package/dist/session-start.mjs +91 -91
  17. package/dist/shim.mjs +81 -81
  18. package/dist/stop.mjs +89 -89
  19. package/dist/subagent-start.mjs +83 -83
  20. package/dist/subagent-stop.mjs +84 -84
  21. package/dist/support.mjs +87 -87
  22. package/dist/tlc-cli.mjs +103 -102
  23. package/dist/tool-after.mjs +83 -83
  24. package/dist/tool-before.mjs +85 -85
  25. package/dist/tool-failure.mjs +83 -83
  26. package/dist/uninstall-runtime.mjs +4 -4
  27. package/docs/concepts.md +4 -1
  28. package/docs/log.md +8 -0
  29. package/package.json +4 -2
  30. package/schema.json +545 -0
  31. package/src/core/capability/capability.store.ts +27 -1
  32. package/src/core/comment-policy/comment-policy.service.ts +3 -8
  33. package/src/core/core.facade.ts +7 -1
  34. package/src/core/floor/floor.policy-surface.ts +1 -1
  35. package/src/core/handoff/handoff.service.ts +30 -0
  36. package/src/core/handoff/handoff.types.ts +0 -2
  37. package/src/core/lesson/lesson.types.ts +5 -0
  38. package/src/core/policy/policy.loader.ts +5 -1
  39. package/src/core/policy/policy.shadow.ts +68 -2
  40. package/src/core/turn/turn.failure-signals.ts +3 -1
  41. package/src/entrypoints/stop.ts +16 -14
  42. package/src/entrypoints/subagent-stop.ts +8 -6
  43. package/tools/doctor.ts +48 -0
  44. package/tools/init-project.ts +19 -1
package/schema.json ADDED
@@ -0,0 +1,545 @@
1
+ {
2
+ "additionalProperties": false,
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "type": "string"
7
+ },
8
+ "version": {
9
+ "const": 1,
10
+ "type": "number"
11
+ },
12
+ "mode": {
13
+ "enum": [
14
+ "focus",
15
+ "paired",
16
+ "solo"
17
+ ],
18
+ "type": "string"
19
+ },
20
+ "projectName": {
21
+ "type": "string"
22
+ },
23
+ "codePaths": {
24
+ "type": "array",
25
+ "items": {
26
+ "type": "string"
27
+ }
28
+ },
29
+ "grind": {
30
+ "$ref": "#/definitions/Partial%3C%7Benabled%3Aboolean%3BmaxLoops%3Anumber%3BlintCommand%3Astring%5B%5D%7Cnull%3BtestCommand%3Astring%5B%5D%7Cnull%3BappendFiles%3Aimport(%22.%2Fsrc%2Fcore%2Fgate%2Fgate.types%22%2C%7Bwith%3A%7B%22resolution-mode%22%3A%22import%22%7D%7D).AppendFilesMode%3B%7D%3E"
31
+ },
32
+ "shipGate": {
33
+ "$ref": "#/definitions/Partial%3C%7Benabled%3Aboolean%3BruntimePathPrefixes%3Astring%5B%5D%3BruntimePathExcludes%3Astring%5B%5D%3BevidenceDir%3Astring%7Cnull%3BevidenceMaxAgeHours%3Anumber%3BemptyDiffAntiShip%3Aboolean%3BclaimWindowMinutes%3Anumber%3B%7D%3E"
34
+ },
35
+ "subagents": {
36
+ "$ref": "#/definitions/Partial%3C%7BenforceAllowlist%3Aboolean%3BrequireModel%3Aboolean%3BallowedModels%3Aimport(%22.%2Fsrc%2Fcore%2Fpolicy%2Fpolicy.types%22%2C%7Bwith%3A%7B%22resolution-mode%22%3A%22import%22%7D%7D).ProviderScoped%3Cstring%3E%3BblockedPatterns%3Aimport(%22.%2Fsrc%2Fcore%2Fpolicy%2Fpolicy.types%22%2C%7Bwith%3A%7B%22resolution-mode%22%3A%22import%22%7D%7D).ProviderScoped%3Cstring%3E%3BminEffort%3A%22low%22%7C%22medium%22%7C%22high%22%7C%22xhigh%22%7C%22max%22%7Cnull%3BblockParentFast%3Aboolean%3BblockMode%3A%22deny%22%7C%22ask%22%3BreadOnlyTypes%3Astring%5B%5D%3B%7D%3E"
37
+ },
38
+ "docs": {
39
+ "$ref": "#/definitions/Partial%3C%7Bcommand%3Astring%5B%5D%7Cnull%3Bseverity%3A%22deny%22%7C%22warn%22%3B%7D%3E"
40
+ },
41
+ "observe": {
42
+ "description": "why: observation runs a rail's checker while the rail is not enforcing, so the harness can tell \"the model\nalready does this\" from \"the rule works\". Opt-in and default empty: it costs a diff scan per turn and answers a\nquestion only an operator who is asking it needs answered.",
43
+ "type": "object",
44
+ "properties": {
45
+ "enabled": {
46
+ "type": "boolean"
47
+ },
48
+ "rails": {
49
+ "description": "Rails to observe by name. An unknown name is inert rather than an error — a rail may not exist yet.",
50
+ "type": "array",
51
+ "items": {
52
+ "type": "string"
53
+ }
54
+ }
55
+ },
56
+ "additionalProperties": false,
57
+ "required": [
58
+ "enabled",
59
+ "rails"
60
+ ]
61
+ },
62
+ "comments": {
63
+ "$ref": "#/definitions/Partial%3C%7Benabled%3Aboolean%3BonViolation%3A%22followup%22%7C%22off%22%3Bmode%3Aimport(%22.%2Fsrc%2Fcore%2Fpolicy%2Fpolicy.types%22%2C%7Bwith%3A%7B%22resolution-mode%22%3A%22import%22%7D%7D).CommentMode%3B%7D%3E"
64
+ },
65
+ "supplyChain": {
66
+ "type": "object",
67
+ "properties": {
68
+ "enabled": {
69
+ "type": "boolean"
70
+ }
71
+ },
72
+ "additionalProperties": false,
73
+ "required": [
74
+ "enabled"
75
+ ]
76
+ },
77
+ "duplication": {
78
+ "type": "object",
79
+ "properties": {
80
+ "enabled": {
81
+ "type": "boolean"
82
+ },
83
+ "minRun": {
84
+ "description": "why: the window is the only knob, because it is the only one calibration moved. Six is where matches stopped being punctuation.",
85
+ "type": "number"
86
+ }
87
+ },
88
+ "additionalProperties": false,
89
+ "required": [
90
+ "enabled",
91
+ "minRun"
92
+ ]
93
+ },
94
+ "obs": {
95
+ "$ref": "#/definitions/Partial%3C%7BglobalSpool%3Aboolean%3BincludePayloads%3Aboolean%3BmaxAttrChars%3Anumber%3BsessionCostAlertUsd%3Anumber%7Cnull%3BretentionDays%3Anumber%3B%7D%3E"
96
+ },
97
+ "untrustedContent": {
98
+ "$ref": "#/definitions/Partial%3C%7Benabled%3Aboolean%3Bmode%3Aimport(%22.%2Fsrc%2Fcore%2Funtrusted%2Funtrusted.types%22%2C%7Bwith%3A%7B%22resolution-mode%22%3A%22import%22%7D%7D).UntrustedMode%3BextraTools%3Astring%5B%5D%3BextraCommandPatterns%3Astring%5B%5D%3B%7D%3E"
99
+ },
100
+ "planGate": {
101
+ "$ref": "#/definitions/Partial%3C%7Benabled%3Aboolean%3BwindowMinutes%3Anumber%3B%7D%3E"
102
+ },
103
+ "rules": {
104
+ "$ref": "#/definitions/Partial%3C%7Benabled%3Aboolean%3B%7D%3E"
105
+ },
106
+ "shell": {
107
+ "$ref": "#/definitions/Partial%3C%7BcatastrophicAsk%3Aboolean%3BstallDetection%3Aboolean%3BstallRepeatThreshold%3Anumber%3B%7D%3E"
108
+ },
109
+ "intelligence": {
110
+ "additionalProperties": false,
111
+ "type": "object",
112
+ "properties": {
113
+ "gapFeedback": {
114
+ "type": "boolean"
115
+ },
116
+ "failureClassification": {
117
+ "type": "boolean"
118
+ },
119
+ "progressiveHandoff": {
120
+ "type": "boolean"
121
+ },
122
+ "progressiveContext": {
123
+ "type": "boolean"
124
+ },
125
+ "autopilot": {
126
+ "type": "boolean"
127
+ },
128
+ "idleTurnGate": {
129
+ "type": "boolean"
130
+ },
131
+ "budgetContinue": {
132
+ "type": "boolean"
133
+ },
134
+ "budgetContinueAfterLoops": {
135
+ "type": "number"
136
+ },
137
+ "lessons": {
138
+ "$ref": "#/definitions/Partial%3Cimport(%22.%2Fsrc%2Fcore%2Fpolicy%2Fpolicy.types%22%2C%7Bwith%3A%7B%22resolution-mode%22%3A%22import%22%7D%7D).LessonsPolicyConfig%3E"
139
+ }
140
+ }
141
+ },
142
+ "mcpPrime": {
143
+ "type": "array",
144
+ "items": {
145
+ "type": "string"
146
+ }
147
+ },
148
+ "bootstrapExtra": {
149
+ "type": "array",
150
+ "items": {
151
+ "type": "string"
152
+ }
153
+ }
154
+ },
155
+ "definitions": {
156
+ "AppendFilesMode": {
157
+ "enum": [
158
+ "always",
159
+ "auto",
160
+ "never"
161
+ ],
162
+ "type": "string"
163
+ },
164
+ "import(\"./src/core/policy/policy.types\",{with:{\"resolution-mode\":\"import\"}}).ProviderScoped<string>": {
165
+ "anyOf": [
166
+ {
167
+ "type": "array",
168
+ "items": {
169
+ "type": "string"
170
+ }
171
+ },
172
+ {
173
+ "$ref": "#/definitions/Record%3Cstring%2Cstring%5B%5D%3E"
174
+ }
175
+ ]
176
+ },
177
+ "Record<string,string[]>": {
178
+ "type": "object",
179
+ "additionalProperties": false
180
+ },
181
+ "CommentMode": {
182
+ "description": "`declared` requires a reason marker, `strict` accepts no added comment at all, and `resolvable` is `declared`\nplus the question neither can ask: does the comment mean anything to a reader who was not in the session that\nwrote it ([/decisions/ad-070.md](/decisions/ad-070.md))?",
183
+ "enum": [
184
+ "declared",
185
+ "resolvable",
186
+ "strict"
187
+ ],
188
+ "type": "string"
189
+ },
190
+ "UntrustedMode": {
191
+ "description": "`frame` states once per turn that outside content is data. `enforce` adds the question framing cannot ask —\ndid this command come from that content — and answers it verbatim\n([/decisions/ad-077.md](/decisions/ad-077.md)).",
192
+ "enum": [
193
+ "enforce",
194
+ "frame"
195
+ ],
196
+ "type": "string"
197
+ },
198
+ "LessonsSyncMode": {
199
+ "description": "How the durable provider view is decided. `auto` asks the host: a provider that does not deliver context from\nits session-start hook has no other route, so the view is written there and not where the hook works\n([/decisions/ad-050.md](/decisions/ad-050.md)).",
200
+ "enum": [
201
+ "always",
202
+ "auto",
203
+ "never"
204
+ ],
205
+ "type": "string"
206
+ },
207
+ "Partial<{enabled:boolean;maxLoops:number;lintCommand:string[]|null;testCommand:string[]|null;appendFiles:import(\"./src/core/gate/gate.types\",{with:{\"resolution-mode\":\"import\"}}).AppendFilesMode;}>": {
208
+ "type": "object",
209
+ "properties": {
210
+ "enabled": {
211
+ "type": "boolean"
212
+ },
213
+ "maxLoops": {
214
+ "type": "number"
215
+ },
216
+ "lintCommand": {
217
+ "anyOf": [
218
+ {
219
+ "type": "array",
220
+ "items": {
221
+ "type": "string"
222
+ }
223
+ },
224
+ {
225
+ "type": "null"
226
+ }
227
+ ]
228
+ },
229
+ "testCommand": {
230
+ "anyOf": [
231
+ {
232
+ "type": "array",
233
+ "items": {
234
+ "type": "string"
235
+ }
236
+ },
237
+ {
238
+ "type": "null"
239
+ }
240
+ ]
241
+ },
242
+ "appendFiles": {
243
+ "enum": [
244
+ "always",
245
+ "auto",
246
+ "never"
247
+ ],
248
+ "type": "string"
249
+ }
250
+ },
251
+ "additionalProperties": false
252
+ },
253
+ "Partial<{enabled:boolean;runtimePathPrefixes:string[];runtimePathExcludes:string[];evidenceDir:string|null;evidenceMaxAgeHours:number;emptyDiffAntiShip:boolean;claimWindowMinutes:number;}>": {
254
+ "type": "object",
255
+ "properties": {
256
+ "enabled": {
257
+ "type": "boolean"
258
+ },
259
+ "runtimePathPrefixes": {
260
+ "type": "array",
261
+ "items": {
262
+ "type": "string"
263
+ }
264
+ },
265
+ "runtimePathExcludes": {
266
+ "type": "array",
267
+ "items": {
268
+ "type": "string"
269
+ }
270
+ },
271
+ "evidenceDir": {
272
+ "type": [
273
+ "null",
274
+ "string"
275
+ ]
276
+ },
277
+ "evidenceMaxAgeHours": {
278
+ "type": "number"
279
+ },
280
+ "emptyDiffAntiShip": {
281
+ "type": "boolean"
282
+ },
283
+ "claimWindowMinutes": {
284
+ "type": "number"
285
+ }
286
+ },
287
+ "additionalProperties": false
288
+ },
289
+ "Partial<{enforceAllowlist:boolean;requireModel:boolean;allowedModels:import(\"./src/core/policy/policy.types\",{with:{\"resolution-mode\":\"import\"}}).ProviderScoped<string>;blockedPatterns:import(\"./src/core/policy/policy.types\",{with:{\"resolution-mode\":\"import\"}}).ProviderScoped<string>;minEffort:\"low\"|\"medium\"|\"high\"|\"xhigh\"|\"max\"|null;blockParentFast:boolean;blockMode:\"deny\"|\"ask\";readOnlyTypes:string[];}>": {
290
+ "type": "object",
291
+ "properties": {
292
+ "enforceAllowlist": {
293
+ "type": "boolean"
294
+ },
295
+ "requireModel": {
296
+ "type": "boolean"
297
+ },
298
+ "allowedModels": {
299
+ "anyOf": [
300
+ {
301
+ "type": "array",
302
+ "items": {
303
+ "type": "string"
304
+ }
305
+ },
306
+ {
307
+ "$ref": "#/definitions/Record%3Cstring%2Cstring%5B%5D%3E"
308
+ }
309
+ ]
310
+ },
311
+ "blockedPatterns": {
312
+ "anyOf": [
313
+ {
314
+ "type": "array",
315
+ "items": {
316
+ "type": "string"
317
+ }
318
+ },
319
+ {
320
+ "$ref": "#/definitions/Record%3Cstring%2Cstring%5B%5D%3E"
321
+ }
322
+ ]
323
+ },
324
+ "minEffort": {
325
+ "anyOf": [
326
+ {
327
+ "enum": [
328
+ "high",
329
+ "low",
330
+ "max",
331
+ "medium",
332
+ "xhigh"
333
+ ],
334
+ "type": "string"
335
+ },
336
+ {
337
+ "type": "null"
338
+ }
339
+ ]
340
+ },
341
+ "blockParentFast": {
342
+ "type": "boolean"
343
+ },
344
+ "blockMode": {
345
+ "enum": [
346
+ "ask",
347
+ "deny"
348
+ ],
349
+ "type": "string"
350
+ },
351
+ "readOnlyTypes": {
352
+ "type": "array",
353
+ "items": {
354
+ "type": "string"
355
+ }
356
+ }
357
+ },
358
+ "additionalProperties": false
359
+ },
360
+ "Partial<{command:string[]|null;severity:\"deny\"|\"warn\";}>": {
361
+ "type": "object",
362
+ "properties": {
363
+ "command": {
364
+ "description": "Null means the gate does not exist for this project.",
365
+ "anyOf": [
366
+ {
367
+ "type": "array",
368
+ "items": {
369
+ "type": "string"
370
+ }
371
+ },
372
+ {
373
+ "type": "null"
374
+ }
375
+ ]
376
+ },
377
+ "severity": {
378
+ "enum": [
379
+ "deny",
380
+ "warn"
381
+ ],
382
+ "type": "string"
383
+ }
384
+ },
385
+ "additionalProperties": false
386
+ },
387
+ "Partial<{enabled:boolean;onViolation:\"followup\"|\"off\";mode:import(\"./src/core/policy/policy.types\",{with:{\"resolution-mode\":\"import\"}}).CommentMode;}>": {
388
+ "type": "object",
389
+ "properties": {
390
+ "enabled": {
391
+ "type": "boolean"
392
+ },
393
+ "onViolation": {
394
+ "enum": [
395
+ "followup",
396
+ "off"
397
+ ],
398
+ "type": "string"
399
+ },
400
+ "mode": {
401
+ "description": "`declared` requires a reason marker, `strict` accepts no added comment at all, and `resolvable` is `declared`\nplus the question neither can ask: does the comment mean anything to a reader who was not in the session that\nwrote it ([/decisions/ad-070.md](/decisions/ad-070.md))?",
402
+ "enum": [
403
+ "declared",
404
+ "resolvable",
405
+ "strict"
406
+ ],
407
+ "type": "string"
408
+ }
409
+ },
410
+ "additionalProperties": false
411
+ },
412
+ "Partial<{globalSpool:boolean;includePayloads:boolean;maxAttrChars:number;sessionCostAlertUsd:number|null;retentionDays:number;}>": {
413
+ "type": "object",
414
+ "properties": {
415
+ "globalSpool": {
416
+ "type": "boolean"
417
+ },
418
+ "includePayloads": {
419
+ "type": "boolean"
420
+ },
421
+ "maxAttrChars": {
422
+ "type": "number"
423
+ },
424
+ "sessionCostAlertUsd": {
425
+ "type": [
426
+ "null",
427
+ "number"
428
+ ]
429
+ },
430
+ "retentionDays": {
431
+ "type": "number"
432
+ }
433
+ },
434
+ "additionalProperties": false
435
+ },
436
+ "Partial<{enabled:boolean;mode:import(\"./src/core/untrusted/untrusted.types\",{with:{\"resolution-mode\":\"import\"}}).UntrustedMode;extraTools:string[];extraCommandPatterns:string[];}>": {
437
+ "type": "object",
438
+ "properties": {
439
+ "enabled": {
440
+ "type": "boolean"
441
+ },
442
+ "mode": {
443
+ "description": "`frame` states once per turn that outside content is data. `enforce` adds the question framing cannot ask —\ndid this command come from that content ([/decisions/ad-077.md](/decisions/ad-077.md))?",
444
+ "enum": [
445
+ "enforce",
446
+ "frame"
447
+ ],
448
+ "type": "string"
449
+ },
450
+ "extraTools": {
451
+ "type": "array",
452
+ "items": {
453
+ "type": "string"
454
+ }
455
+ },
456
+ "extraCommandPatterns": {
457
+ "type": "array",
458
+ "items": {
459
+ "type": "string"
460
+ }
461
+ }
462
+ },
463
+ "additionalProperties": false
464
+ },
465
+ "Partial<{enabled:boolean;windowMinutes:number;}>": {
466
+ "type": "object",
467
+ "properties": {
468
+ "enabled": {
469
+ "type": "boolean"
470
+ },
471
+ "windowMinutes": {
472
+ "type": "number"
473
+ }
474
+ },
475
+ "additionalProperties": false
476
+ },
477
+ "Partial<{enabled:boolean;}>": {
478
+ "type": "object",
479
+ "properties": {
480
+ "enabled": {
481
+ "type": "boolean"
482
+ }
483
+ },
484
+ "additionalProperties": false
485
+ },
486
+ "Partial<{catastrophicAsk:boolean;stallDetection:boolean;stallRepeatThreshold:number;}>": {
487
+ "type": "object",
488
+ "properties": {
489
+ "catastrophicAsk": {
490
+ "type": "boolean"
491
+ },
492
+ "stallDetection": {
493
+ "type": "boolean"
494
+ },
495
+ "stallRepeatThreshold": {
496
+ "type": "number"
497
+ }
498
+ },
499
+ "additionalProperties": false
500
+ },
501
+ "Partial<import(\"./src/core/policy/policy.types\",{with:{\"resolution-mode\":\"import\"}}).LessonsPolicyConfig>": {
502
+ "type": "object",
503
+ "properties": {
504
+ "enabled": {
505
+ "type": "boolean"
506
+ },
507
+ "maxInjectSession": {
508
+ "type": "number"
509
+ },
510
+ "maxInjectRetry": {
511
+ "type": "number"
512
+ },
513
+ "maxCharsSession": {
514
+ "type": "number"
515
+ },
516
+ "maxCharsRetry": {
517
+ "type": "number"
518
+ },
519
+ "promoteHitCount": {
520
+ "type": "number"
521
+ },
522
+ "decayLambda": {
523
+ "type": "number"
524
+ },
525
+ "projectBoost": {
526
+ "type": "number"
527
+ },
528
+ "syncRulesFile": {
529
+ "description": "How the durable provider view is decided. `auto` asks the host: a provider that does not deliver context from\nits session-start hook has no other route, so the view is written there and not where the hook works\n([/decisions/ad-050.md](/decisions/ad-050.md)).",
530
+ "enum": [
531
+ "always",
532
+ "auto",
533
+ "never"
534
+ ],
535
+ "type": "string"
536
+ },
537
+ "gardenOnSessionEnd": {
538
+ "type": "boolean"
539
+ }
540
+ },
541
+ "additionalProperties": false
542
+ }
543
+ },
544
+ "$schema": "http://json-schema.org/draft-07/schema#"
545
+ }
@@ -27,8 +27,34 @@ export function loadCatalog(home = runtimeHome()): CapabilityCatalog | null {
27
27
  return raw;
28
28
  }
29
29
 
30
+ export type ConfigReadStatus =
31
+ | { status: "absent" }
32
+ | { status: "parsed"; value: Record<string, unknown> }
33
+ | { status: "malformed"; error: string };
34
+
35
+ /**
36
+ * why: `readJson` already reads this file but collapses "absent" and "malformed" to the same `null`
37
+ * — the exact silent-corruption gap `doctor` needs to close, so this is a second, tagged reader.
38
+ */
39
+ export function readProjectPolicyStatus(projectDir: string): ConfigReadStatus {
40
+ const path = projectConfigPath(projectDir);
41
+ if (!existsSync(path)) {
42
+ return { status: "absent" };
43
+ }
44
+ try {
45
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
46
+ // why: a JSON Schema meta-key, not a Policy field — see the identical strip in policy.loader.ts,
47
+ // this is the second, independent read of the same file.
48
+ delete parsed.$schema;
49
+ return { status: "parsed", value: parsed };
50
+ } catch (error) {
51
+ return { status: "malformed", error: error instanceof Error ? error.message : String(error) };
52
+ }
53
+ }
54
+
30
55
  export function readProjectPolicyRaw(projectDir: string): Record<string, unknown> | null {
31
- return readJson<Record<string, unknown>>(projectConfigPath(projectDir));
56
+ const result = readProjectPolicyStatus(projectDir);
57
+ return result.status === "parsed" ? result.value : null;
32
58
  }
33
59
 
34
60
  function runtimeSeenPath(projectDir: string): string {
@@ -62,14 +62,9 @@ export function isCommentLine(text: string, file = ""): boolean {
62
62
  }
63
63
 
64
64
  /**
65
- * hazard: the rail's own scan target used to be `filterCodeTargets`, a nine-extension regex shared with grind
66
- * and duplication. The syntax catalog already knows 40+ languages — Terraform, SQL, YAML, Ruby, Java among
67
- * them — so a project whose changed files never carried one of the nine extensions had `mode: "strict"` in its
68
- * config and zero comments ever scanned. This filters by the same source the scanner itself trusts.
69
- *
70
- * invariant: takes no `codePaths` — that would need `policy.loader.ts`, which imports `policy.defaults.ts`,
71
- * which imports `duplication.service.ts`, which imports this module for `matchesSyntax`. A caller scopes by
72
- * `codePaths` itself first; this filters what is left by syntax alone.
65
+ * invariant: no `codePaths` param. Taking one needs `policy.loader.ts` → `policy.defaults.ts` →
66
+ * `duplication.service.ts`, which imports this module back for `matchesSyntax` — an import cycle. The
67
+ * caller scopes by `codePaths` first; this filters what's left by syntax alone.
73
68
  */
74
69
  export function filterCommentTargets(relativePaths: string[]): string[] {
75
70
  return relativePaths.filter((path) => syntaxFor(path) !== null);
@@ -16,6 +16,7 @@ import {
16
16
  import {
17
17
  loadCatalog,
18
18
  readProjectPolicyRaw,
19
+ readProjectPolicyStatus,
19
20
  readRuntimeSeen,
20
21
  writeRuntimeSeen,
21
22
  } from "./capability/capability.store.ts";
@@ -48,6 +49,7 @@ import { cachedVerdict, computeInputsHash, isCacheHit } from "./gate/gate.inputs
48
49
  import { describeHolder, GATE_LOCK_WAIT_MS, GateLockTimeoutError, withGateLock } from "./gate/gate.lock.ts";
49
50
  import { gapsFromArtifact } from "./gate/gate.service.ts";
50
51
  import {
52
+ clearStuckSignals,
51
53
  handoffInjectable,
52
54
  patchHandoff,
53
55
  readForeignSlices,
@@ -133,7 +135,7 @@ import {
133
135
  import { operatorBootstrapLines } from "./policy/policy.operator.ts";
134
136
  import { isOperatorMode, OPERATOR_MODES } from "./policy/policy.posture.ts";
135
137
  import { activeRails } from "./policy/policy.rails.ts";
136
- import { pruneShadowed, shadowedKeys } from "./policy/policy.shadow.ts";
138
+ import { pruneShadowed, shadowedKeys, typeMismatches, unknownKeys } from "./policy/policy.shadow.ts";
137
139
  import { forProvider } from "./policy/policy.types.ts";
138
140
  import { checkCollision, heartbeat, register, release, sweepStale } from "./presence/presence.service.ts";
139
141
  import { freshness, freshnessMessage, mayReplace, shouldRefetch } from "./pricing/pricing.freshness.ts";
@@ -264,6 +266,7 @@ export const coreFacade = {
264
266
  ENABLE_HINT,
265
267
  loadCatalog,
266
268
  readProjectPolicyRaw,
269
+ readProjectPolicyStatus,
267
270
  readRuntimeSeen,
268
271
  writeRuntimeSeen,
269
272
  isAvailableNotEnabled,
@@ -306,6 +309,7 @@ export const coreFacade = {
306
309
  readHandoff,
307
310
  readHandoffFile,
308
311
  readForeignSlices,
312
+ clearStuckSignals,
309
313
  },
310
314
  shim: {
311
315
  coversHandler,
@@ -416,6 +420,8 @@ export const coreFacade = {
416
420
  resolvedWithoutProjectTier,
417
421
  shadowedKeys,
418
422
  pruneShadowed,
423
+ unknownKeys,
424
+ typeMismatches,
419
425
  resolveProjectPosture,
420
426
  resolveProjectSyncMode,
421
427
  OPERATOR_MODES,
@@ -99,7 +99,7 @@ const HARNESS_BINS = new Set(["tlc", "tlc.cmd"]);
99
99
  // why: `policy` is here for the sharpest reason of any of them — `tlc harness policy accept` exists to clear a
100
100
  // tampering signal, so an agent that could reach it would make the whole integrity rail decorative. It is refused
101
101
  // by a floor rule with no config switch, exactly like the rest ([/decisions/ad-030.md](/decisions/ad-030.md)).
102
- const MUTATING_SUBCOMMANDS = new Set(["pause", "resume", "grind", "mode", "init", "gate", "policy"]);
102
+ const MUTATING_SUBCOMMANDS = new Set(["pause", "resume", "reset", "grind", "mode", "init", "gate", "policy"]);
103
103
 
104
104
  function deny(detail: string, note: string, remedy?: string): PolicySurfaceVerdict {
105
105
  return { kind: "deny", detail, note, ...(remedy ? { remedy } : {}) };