@tech-leads-club/harness-toolkit 0.2.1

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 (300) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/LICENSE +84 -0
  3. package/NOTICE +9 -0
  4. package/README.md +480 -0
  5. package/bin/tlc +10 -0
  6. package/bin/tlc-build +80 -0
  7. package/bin/tlc-cli.ts +1509 -0
  8. package/bin/tlc-exec +10 -0
  9. package/bin/tlc-exec.cmd +4 -0
  10. package/bin/tlc-exec.d.mts +60 -0
  11. package/bin/tlc-exec.mjs +248 -0
  12. package/bin/tlc.cmd +4 -0
  13. package/bin/tlc.mjs +7 -0
  14. package/bin/write-user-hooks.d.mts +40 -0
  15. package/bin/write-user-hooks.mjs +123 -0
  16. package/capabilities/catalog.json +418 -0
  17. package/config.example.json +51 -0
  18. package/dist/compact-before.mjs +7839 -0
  19. package/dist/doctor.mjs +8263 -0
  20. package/dist/help-topic.mjs +82 -0
  21. package/dist/init-project.mjs +993 -0
  22. package/dist/install-runtime.mjs +969 -0
  23. package/dist/lessons-cli.mjs +7258 -0
  24. package/dist/obs-cli.mjs +7025 -0
  25. package/dist/price-lookup.mjs +221 -0
  26. package/dist/prompt-submit.mjs +7860 -0
  27. package/dist/refresh-model-prices.mjs +158 -0
  28. package/dist/response-after.mjs +7879 -0
  29. package/dist/run.mjs +7830 -0
  30. package/dist/session-end.mjs +7937 -0
  31. package/dist/session-start.mjs +8056 -0
  32. package/dist/shim.mjs +52 -0
  33. package/dist/stop.mjs +8494 -0
  34. package/dist/subagent-start.mjs +7861 -0
  35. package/dist/subagent-stop.mjs +7851 -0
  36. package/dist/support.mjs +7045 -0
  37. package/dist/tlc-cli.mjs +8063 -0
  38. package/dist/tool-after.mjs +8113 -0
  39. package/dist/tool-before.mjs +7985 -0
  40. package/dist/tool-failure.mjs +7850 -0
  41. package/dist/uninstall-runtime.mjs +1307 -0
  42. package/docs/architecture.md +270 -0
  43. package/docs/concepts.md +562 -0
  44. package/docs/coverage.md +55 -0
  45. package/docs/decisions/ad-001.md +32 -0
  46. package/docs/decisions/ad-002.md +51 -0
  47. package/docs/decisions/ad-003.md +30 -0
  48. package/docs/decisions/ad-004.md +37 -0
  49. package/docs/decisions/ad-005.md +36 -0
  50. package/docs/decisions/ad-006.md +49 -0
  51. package/docs/decisions/ad-007.md +36 -0
  52. package/docs/decisions/ad-008.md +54 -0
  53. package/docs/decisions/ad-009.md +61 -0
  54. package/docs/decisions/ad-010.md +45 -0
  55. package/docs/decisions/ad-011.md +59 -0
  56. package/docs/decisions/ad-012.md +71 -0
  57. package/docs/decisions/ad-013.md +87 -0
  58. package/docs/decisions/ad-014.md +56 -0
  59. package/docs/decisions/ad-015.md +33 -0
  60. package/docs/decisions/ad-016.md +98 -0
  61. package/docs/decisions/ad-017.md +65 -0
  62. package/docs/decisions/ad-018.md +77 -0
  63. package/docs/decisions/ad-019.md +75 -0
  64. package/docs/decisions/ad-020.md +88 -0
  65. package/docs/decisions/ad-021.md +57 -0
  66. package/docs/decisions/ad-022.md +120 -0
  67. package/docs/decisions/ad-023.md +87 -0
  68. package/docs/decisions/ad-024.md +85 -0
  69. package/docs/decisions/ad-025.md +92 -0
  70. package/docs/decisions/ad-026.md +131 -0
  71. package/docs/decisions/ad-027.md +90 -0
  72. package/docs/decisions/ad-028.md +90 -0
  73. package/docs/decisions/ad-029.md +73 -0
  74. package/docs/decisions/ad-030.md +97 -0
  75. package/docs/decisions/ad-031.md +94 -0
  76. package/docs/decisions/ad-032.md +83 -0
  77. package/docs/decisions/ad-033.md +89 -0
  78. package/docs/decisions/ad-034.md +86 -0
  79. package/docs/decisions/ad-035.md +86 -0
  80. package/docs/decisions/ad-036.md +68 -0
  81. package/docs/decisions/ad-037.md +47 -0
  82. package/docs/decisions/ad-038.md +52 -0
  83. package/docs/decisions/ad-039.md +69 -0
  84. package/docs/decisions/ad-040.md +89 -0
  85. package/docs/decisions/ad-041.md +98 -0
  86. package/docs/decisions/ad-042.md +82 -0
  87. package/docs/decisions/ad-043.md +79 -0
  88. package/docs/decisions/ad-044.md +61 -0
  89. package/docs/decisions/ad-045.md +94 -0
  90. package/docs/decisions/ad-046.md +111 -0
  91. package/docs/decisions/ad-047.md +96 -0
  92. package/docs/decisions/ad-048.md +85 -0
  93. package/docs/decisions/ad-049.md +66 -0
  94. package/docs/decisions/ad-050.md +94 -0
  95. package/docs/decisions/ad-051.md +69 -0
  96. package/docs/decisions/ad-052.md +69 -0
  97. package/docs/decisions/ad-053.md +78 -0
  98. package/docs/decisions/ad-054.md +98 -0
  99. package/docs/decisions/ad-055.md +74 -0
  100. package/docs/decisions/ad-056.md +85 -0
  101. package/docs/decisions/ad-057.md +68 -0
  102. package/docs/decisions/ad-058.md +97 -0
  103. package/docs/decisions/ad-059.md +82 -0
  104. package/docs/decisions/ad-060.md +75 -0
  105. package/docs/decisions/ad-061.md +68 -0
  106. package/docs/decisions/ad-062.md +72 -0
  107. package/docs/decisions/ad-063.md +84 -0
  108. package/docs/decisions/ad-064.md +79 -0
  109. package/docs/decisions/ad-065.md +81 -0
  110. package/docs/decisions/ad-066.md +111 -0
  111. package/docs/decisions/ad-067.md +64 -0
  112. package/docs/decisions/ad-068.md +79 -0
  113. package/docs/decisions/ad-069.md +74 -0
  114. package/docs/decisions/ad-070.md +86 -0
  115. package/docs/decisions/ad-071.md +93 -0
  116. package/docs/decisions/ad-072.md +82 -0
  117. package/docs/decisions/ad-073.md +102 -0
  118. package/docs/decisions/ad-074.md +91 -0
  119. package/docs/decisions/ad-075.md +79 -0
  120. package/docs/decisions/ad-076.md +102 -0
  121. package/docs/decisions/ad-077.md +94 -0
  122. package/docs/decisions/ad-078.md +84 -0
  123. package/docs/decisions/ad-079.md +73 -0
  124. package/docs/decisions/ad-080.md +86 -0
  125. package/docs/decisions/ad-081.md +70 -0
  126. package/docs/decisions/ad-082.md +79 -0
  127. package/docs/decisions/ad-083.md +88 -0
  128. package/docs/decisions/index.md +111 -0
  129. package/docs/diagnose.md +248 -0
  130. package/docs/index.md +52 -0
  131. package/docs/init.md +101 -0
  132. package/docs/lessons.md +294 -0
  133. package/docs/log.md +139 -0
  134. package/docs/measure.md +178 -0
  135. package/docs/providers/claude-code.md +118 -0
  136. package/docs/providers/cursor.md +110 -0
  137. package/docs/providers/index.md +71 -0
  138. package/docs/troubleshooting.md +138 -0
  139. package/model-aliases.json +12 -0
  140. package/model-prices.cursor.json +410 -0
  141. package/model-prices.json +1 -0
  142. package/package.json +57 -0
  143. package/skills/harness-init/SKILL.md +241 -0
  144. package/skills/harness-init/evals/trigger_evals.json +86 -0
  145. package/skills/harness-init/references/capabilities.md +113 -0
  146. package/src/contracts/capabilities.ts +30 -0
  147. package/src/contracts/decision.ts +21 -0
  148. package/src/contracts/effort.ts +15 -0
  149. package/src/contracts/harness-event.ts +87 -0
  150. package/src/contracts/index.ts +6 -0
  151. package/src/contracts/policy-defaults.ts +14 -0
  152. package/src/contracts/wiring.ts +20 -0
  153. package/src/core/attest/attest.service.ts +0 -0
  154. package/src/core/capability/capability.service.ts +62 -0
  155. package/src/core/capability/capability.store.ts +51 -0
  156. package/src/core/capability/capability.types.ts +56 -0
  157. package/src/core/comment-policy/comment-policy.service.ts +307 -0
  158. package/src/core/comment-policy/comment-policy.types.ts +6 -0
  159. package/src/core/comment-policy/comment-resolvability.ts +134 -0
  160. package/src/core/comment-policy/comment-syntax.catalog.ts +310 -0
  161. package/src/core/comment-policy/comment-syntax.store.ts +65 -0
  162. package/src/core/comment-policy/comment-syntax.types.ts +24 -0
  163. package/src/core/core.facade.ts +461 -0
  164. package/src/core/duplication/duplication.service.ts +219 -0
  165. package/src/core/floor/floor.catalog.ts +54 -0
  166. package/src/core/floor/floor.paths.ts +90 -0
  167. package/src/core/floor/floor.policy-surface.ts +311 -0
  168. package/src/core/floor/floor.service.ts +306 -0
  169. package/src/core/floor/floor.tokenize.ts +179 -0
  170. package/src/core/floor/floor.verb.ts +28 -0
  171. package/src/core/floor/index.ts +1 -0
  172. package/src/core/gate/gate.artifact.ts +157 -0
  173. package/src/core/gate/gate.command.ts +130 -0
  174. package/src/core/gate/gate.findings.ts +201 -0
  175. package/src/core/gate/gate.inputs.ts +0 -0
  176. package/src/core/gate/gate.lock.ts +283 -0
  177. package/src/core/gate/gate.service.ts +28 -0
  178. package/src/core/gate/gate.types.ts +67 -0
  179. package/src/core/handoff/handoff.service.ts +43 -0
  180. package/src/core/handoff/handoff.store.ts +59 -0
  181. package/src/core/handoff/handoff.types.ts +89 -0
  182. package/src/core/index.ts +52 -0
  183. package/src/core/integrity/state-seal.ts +81 -0
  184. package/src/core/lesson/lesson.authored.ts +96 -0
  185. package/src/core/lesson/lesson.credit.ts +51 -0
  186. package/src/core/lesson/lesson.garden.ts +283 -0
  187. package/src/core/lesson/lesson.link.ts +93 -0
  188. package/src/core/lesson/lesson.score.ts +61 -0
  189. package/src/core/lesson/lesson.select.ts +209 -0
  190. package/src/core/lesson/lesson.service.ts +110 -0
  191. package/src/core/lesson/lesson.store.ts +317 -0
  192. package/src/core/lesson/lesson.sync.ts +70 -0
  193. package/src/core/lesson/lesson.types.ts +92 -0
  194. package/src/core/lesson/lesson.validity.ts +44 -0
  195. package/src/core/observability/observability.report.ts +326 -0
  196. package/src/core/observability/observability.service.ts +328 -0
  197. package/src/core/observability/observability.store.ts +269 -0
  198. package/src/core/observability/observability.types.ts +193 -0
  199. package/src/core/observability/observability.why.ts +206 -0
  200. package/src/core/observe/observe.service.ts +76 -0
  201. package/src/core/plan/plan.detect.ts +47 -0
  202. package/src/core/plan/plan.service.ts +81 -0
  203. package/src/core/plan/plan.types.ts +25 -0
  204. package/src/core/policy/policy.defaults.ts +103 -0
  205. package/src/core/policy/policy.guard.ts +34 -0
  206. package/src/core/policy/policy.integrity.ts +238 -0
  207. package/src/core/policy/policy.loader.ts +118 -0
  208. package/src/core/policy/policy.operator.ts +82 -0
  209. package/src/core/policy/policy.posture.ts +79 -0
  210. package/src/core/policy/policy.rails.ts +45 -0
  211. package/src/core/policy/policy.types.ts +157 -0
  212. package/src/core/presence/presence.service.ts +111 -0
  213. package/src/core/presence/presence.store.ts +59 -0
  214. package/src/core/presence/presence.types.ts +9 -0
  215. package/src/core/release/release.decisions.ts +147 -0
  216. package/src/core/release/release.seen.ts +38 -0
  217. package/src/core/shell-policy/shell-policy.service.ts +215 -0
  218. package/src/core/shell-policy/shell-policy.stall.ts +57 -0
  219. package/src/core/shell-policy/shell-policy.types.ts +26 -0
  220. package/src/core/ship/ship.ledger.ts +76 -0
  221. package/src/core/ship/ship.service.ts +137 -0
  222. package/src/core/ship/ship.types.ts +19 -0
  223. package/src/core/stagnation/stagnation.resolution.ts +87 -0
  224. package/src/core/stagnation/stagnation.service.ts +20 -0
  225. package/src/core/stagnation/stagnation.store.ts +49 -0
  226. package/src/core/stagnation/stagnation.types.ts +12 -0
  227. package/src/core/subagent-policy/subagent-policy.parent-model.ts +202 -0
  228. package/src/core/subagent-policy/subagent-policy.service.ts +139 -0
  229. package/src/core/subagent-policy/subagent-policy.types.ts +22 -0
  230. package/src/core/supply-chain/supply-chain.catalog.ts +51 -0
  231. package/src/core/supply-chain/supply-chain.service.ts +217 -0
  232. package/src/core/turn/turn.activity.ts +94 -0
  233. package/src/core/turn/turn.autopilot.ts +131 -0
  234. package/src/core/turn/turn.failure-signals.ts +193 -0
  235. package/src/core/turn/turn.loop-counter.ts +71 -0
  236. package/src/core/turn/turn.types.ts +14 -0
  237. package/src/core/untrusted/untrusted.detect.ts +53 -0
  238. package/src/core/untrusted/untrusted.recall.ts +87 -0
  239. package/src/core/untrusted/untrusted.service.ts +129 -0
  240. package/src/core/untrusted/untrusted.store.ts +75 -0
  241. package/src/core/untrusted/untrusted.types.ts +42 -0
  242. package/src/entrypoints/compact-before.ts +14 -0
  243. package/src/entrypoints/prompt-submit.ts +25 -0
  244. package/src/entrypoints/response-after.ts +60 -0
  245. package/src/entrypoints/run.ts +179 -0
  246. package/src/entrypoints/session-end.ts +58 -0
  247. package/src/entrypoints/session-start.ts +172 -0
  248. package/src/entrypoints/shim.ts +46 -0
  249. package/src/entrypoints/stop.ts +876 -0
  250. package/src/entrypoints/subagent-start.ts +16 -0
  251. package/src/entrypoints/subagent-stop.ts +32 -0
  252. package/src/entrypoints/support.ts +182 -0
  253. package/src/entrypoints/tool-after.ts +111 -0
  254. package/src/entrypoints/tool-before.ts +186 -0
  255. package/src/entrypoints/tool-failure.ts +26 -0
  256. package/src/platform/backoff.ts +49 -0
  257. package/src/platform/cli-output.ts +30 -0
  258. package/src/platform/env-scope.ts +22 -0
  259. package/src/platform/fs-atomic.ts +142 -0
  260. package/src/platform/fs-jsonl.ts +24 -0
  261. package/src/platform/git.ts +187 -0
  262. package/src/platform/paths.ts +80 -0
  263. package/src/platform/pricing.ts +229 -0
  264. package/src/platform/process.ts +64 -0
  265. package/src/platform/sanitize.ts +23 -0
  266. package/src/platform/screen.ts +46 -0
  267. package/src/platform/style.ts +103 -0
  268. package/src/providers/claude/claude.capabilities.ts +25 -0
  269. package/src/providers/claude/claude.detect.ts +14 -0
  270. package/src/providers/claude/claude.inbound.ts +252 -0
  271. package/src/providers/claude/claude.lessons-view.ts +33 -0
  272. package/src/providers/claude/claude.outbound.ts +65 -0
  273. package/src/providers/claude/claude.policy-defaults.ts +9 -0
  274. package/src/providers/claude/claude.transcript.ts +62 -0
  275. package/src/providers/claude/claude.wiring.ts +245 -0
  276. package/src/providers/claude/index.ts +17 -0
  277. package/src/providers/cursor/cursor.capabilities.ts +28 -0
  278. package/src/providers/cursor/cursor.detect.ts +14 -0
  279. package/src/providers/cursor/cursor.inbound.ts +203 -0
  280. package/src/providers/cursor/cursor.lessons-view.ts +30 -0
  281. package/src/providers/cursor/cursor.outbound.ts +43 -0
  282. package/src/providers/cursor/cursor.policy-defaults.ts +9 -0
  283. package/src/providers/cursor/cursor.wiring.ts +214 -0
  284. package/src/providers/cursor/index.ts +17 -0
  285. package/src/providers/index.ts +9 -0
  286. package/src/providers/provider.degrade.ts +152 -0
  287. package/src/providers/provider.port.ts +20 -0
  288. package/src/providers/provider.registry.ts +28 -0
  289. package/tools/doctor.ts +631 -0
  290. package/tools/help-topic.ts +74 -0
  291. package/tools/init-project.ts +277 -0
  292. package/tools/install-runtime.ts +164 -0
  293. package/tools/lessons-cli.ts +474 -0
  294. package/tools/obs-cli.ts +175 -0
  295. package/tools/price-lookup.ts +57 -0
  296. package/tools/refresh-model-prices.ts +210 -0
  297. package/tools/test-env.mjs +28 -0
  298. package/tools/test-env.names.d.mts +1 -0
  299. package/tools/test-env.names.mjs +14 -0
  300. package/tools/uninstall-runtime.ts +463 -0
@@ -0,0 +1,461 @@
1
+ import {
2
+ appendAttestation,
3
+ attestationPath,
4
+ fingerprintOf,
5
+ readAttestations,
6
+ verifyChain,
7
+ } from "./attest/attest.service.ts";
8
+ import {
9
+ formatAvailableInventory,
10
+ formatCapabilityDigest,
11
+ formatDoctorWarn,
12
+ isAvailableNotEnabled,
13
+ listAvailableNotEnabled,
14
+ listNewlyAnnounceable,
15
+ } from "./capability/capability.service.ts";
16
+ import {
17
+ loadCatalog,
18
+ readProjectPolicyRaw,
19
+ readRuntimeSeen,
20
+ writeRuntimeSeen,
21
+ } from "./capability/capability.store.ts";
22
+ import { ENABLE_HINT } from "./capability/capability.types.ts";
23
+ import {
24
+ commentViolationMessage,
25
+ declaresReason,
26
+ findAddedComments,
27
+ isCommentLine,
28
+ scanAddedComments,
29
+ } from "./comment-policy/comment-policy.service.ts";
30
+ import { KNOWN_EXTENSION_COUNT, unknownExtensions } from "./comment-policy/comment-syntax.store.ts";
31
+ import {
32
+ duplicationMessage,
33
+ findDuplications,
34
+ MIN_RUN,
35
+ scanProject,
36
+ } from "./duplication/duplication.service.ts";
37
+ import { evaluateFloor } from "./floor/floor.service.ts";
38
+ import { computeGateFingerprint, readLastGate, writeLastGate } from "./gate/gate.artifact.ts";
39
+ import {
40
+ appendFilesVerdict,
41
+ isCommandResolutionFailure,
42
+ isRecipeRunner,
43
+ shouldAppendFiles,
44
+ } from "./gate/gate.command.ts";
45
+ import { filesFromOutput } from "./gate/gate.findings.ts";
46
+ import { cachedVerdict, computeInputsHash, isCacheHit } from "./gate/gate.inputs.ts";
47
+ import { describeHolder, GATE_LOCK_WAIT_MS, GateLockTimeoutError, withGateLock } from "./gate/gate.lock.ts";
48
+ import { gapsFromArtifact } from "./gate/gate.service.ts";
49
+ import {
50
+ handoffInjectable,
51
+ patchHandoff,
52
+ readForeignSlices,
53
+ readHandoff,
54
+ readHandoffFile,
55
+ } from "./handoff/handoff.service.ts";
56
+ import { authoredLessonId, buildAuthoredLesson } from "./lesson/lesson.authored.ts";
57
+ import type { LessonVerdict } from "./lesson/lesson.credit.ts";
58
+ import { effectivenessLine, helpRate, lessonEffectiveness } from "./lesson/lesson.credit.ts";
59
+ import { gardenAndPersistLessons, promotionCount, renderLessonsMarkdown } from "./lesson/lesson.garden.ts";
60
+ import { formatLessonLink, isStaleLesson, lessonLinkVerdict, parseLessonLink } from "./lesson/lesson.link.ts";
61
+ import {
62
+ appliesHere,
63
+ isInjectable,
64
+ renderLessonBlock,
65
+ selectLessons as selectLessonsInner,
66
+ } from "./lesson/lesson.select.ts";
67
+ import { recordLessonFromFailure } from "./lesson/lesson.service.ts";
68
+ import {
69
+ allLessons,
70
+ creditLessons as creditLessonsInner,
71
+ globalLessonsStorePath,
72
+ markGradeable,
73
+ projectLessonsInjectable,
74
+ readGlobalLessons,
75
+ readProjectLessons,
76
+ touchAccessed as touchAccessedInner,
77
+ upsertLesson as upsertLessonInner,
78
+ upsertProjectLesson as upsertProjectLessonInner,
79
+ writeProjectLessons as writeProjectLessonsInner,
80
+ } from "./lesson/lesson.store.ts";
81
+ import { durableViewVerdict, resolveSyncMode } from "./lesson/lesson.sync.ts";
82
+ import type { HarnessLesson, LessonTier } from "./lesson/lesson.types.ts";
83
+ import { validityReason } from "./lesson/lesson.validity.ts";
84
+ import {
85
+ groupByProvider,
86
+ railsNeverFired,
87
+ sessionReportMarkdown,
88
+ sessionReportText,
89
+ } from "./observability/observability.report.ts";
90
+ import {
91
+ DEFAULT_OBS,
92
+ recordAudit,
93
+ recordFromEvent,
94
+ recordObs,
95
+ } from "./observability/observability.service.ts";
96
+ import { getRollup, pruneObs, pruneSpool, readSignalEvents } from "./observability/observability.store.ts";
97
+ import {
98
+ decisionsFrom,
99
+ NOTHING_WAS_THE_HARNESS,
100
+ summarise,
101
+ whyText,
102
+ } from "./observability/observability.why.ts";
103
+ import {
104
+ isObservableRail,
105
+ OBSERVABLE_RAILS,
106
+ observeAttrs,
107
+ shouldObserve,
108
+ unobservableRails,
109
+ } from "./observe/observe.service.ts";
110
+ import { detectDeviations, detectPlan } from "./plan/plan.detect.ts";
111
+ import { evaluatePlanGate, planVerdict } from "./plan/plan.service.ts";
112
+ import { guardPolicySurface } from "./policy/policy.guard.ts";
113
+ import {
114
+ acceptPolicySources,
115
+ allDivergedPaths,
116
+ checkPolicyBaseline,
117
+ divergedPaths,
118
+ policySourceFingerprint,
119
+ recordPolicyBaseline,
120
+ refreshPolicyBaselines,
121
+ } from "./policy/policy.integrity.ts";
122
+ import {
123
+ isUnderCodePaths,
124
+ loadPolicy,
125
+ resolveProjectPosture,
126
+ resolveProjectSyncMode,
127
+ } from "./policy/policy.loader.ts";
128
+ import { operatorBootstrapLines } from "./policy/policy.operator.ts";
129
+ import { isOperatorMode, OPERATOR_MODES } from "./policy/policy.posture.ts";
130
+ import { activeRails } from "./policy/policy.rails.ts";
131
+ import { forProvider } from "./policy/policy.types.ts";
132
+ import { checkCollision, heartbeat, register, release, sweepStale } from "./presence/presence.service.ts";
133
+ import {
134
+ allDecisionFiles,
135
+ formatDecisionDigest,
136
+ needsAction,
137
+ readDecision,
138
+ readDecisions,
139
+ } from "./release/release.decisions.ts";
140
+ import { readReleaseSeen, writeReleaseSeen } from "./release/release.seen.ts";
141
+ import { evaluateShellCommand } from "./shell-policy/shell-policy.service.ts";
142
+ import { clearShellStall } from "./shell-policy/shell-policy.stall.ts";
143
+ import { appendShipLedger, hasRecentEvidence, newestChangeMs, readShipLedger } from "./ship/ship.ledger.ts";
144
+ import {
145
+ detectShipClaim,
146
+ evaluateEmptyDiffAntiShip,
147
+ evaluateShipEvidenceGate,
148
+ recentShipClaimActive,
149
+ touchesRuntime,
150
+ } from "./ship/ship.service.ts";
151
+ import {
152
+ recordResolution,
153
+ resolutionFor,
154
+ resolutionHistoryLine,
155
+ } from "./stagnation/stagnation.resolution.ts";
156
+ import { computeFingerprint } from "./stagnation/stagnation.service.ts";
157
+ import { clearFingerprint, fingerprintHits, trackFingerprint } from "./stagnation/stagnation.store.ts";
158
+ import {
159
+ readParentModelState,
160
+ upsertParentModelState,
161
+ } from "./subagent-policy/subagent-policy.parent-model.ts";
162
+ import { evaluateSubagentSpawn } from "./subagent-policy/subagent-policy.service.ts";
163
+ import { inspectSupplyChain, isManifest, supplyChainMessage } from "./supply-chain/supply-chain.service.ts";
164
+ import { endedWithoutActing, idleTurnMessage, readTurnActivity } from "./turn/turn.activity.ts";
165
+ import { formatAutopilotBlock, resolveAutopilot } from "./turn/turn.autopilot.ts";
166
+ import {
167
+ buildGaps,
168
+ classifyGateFailure,
169
+ formatCarriedGaps,
170
+ formatGapFeedback,
171
+ formatProgressiveContext,
172
+ mergeGaps,
173
+ suggestionFor,
174
+ } from "./turn/turn.failure-signals.ts";
175
+ import {
176
+ checkLoopCap,
177
+ currentLoopCount,
178
+ effectiveLoopCount,
179
+ markBooted,
180
+ nextLoop,
181
+ resetLoop,
182
+ } from "./turn/turn.loop-counter.ts";
183
+ import { findInRecall, recallMessage, remember } from "./untrusted/untrusted.recall.ts";
184
+ import {
185
+ askIfFromUntrusted,
186
+ evaluateUntrustedContent,
187
+ rememberUntrustedOutput,
188
+ } from "./untrusted/untrusted.service.ts";
189
+ import { clearFramingMarker, clearRecall, readRecall } from "./untrusted/untrusted.store.ts";
190
+
191
+ async function selectLessons(
192
+ args: Parameters<typeof selectLessonsInner>[0],
193
+ ): Promise<{ lessons: HarnessLesson[]; usedIds: string[]; omitted: number }> {
194
+ return await selectLessonsInner(args);
195
+ }
196
+
197
+ async function touchAccessed(root: string, ids: string[], now?: Date): Promise<void> {
198
+ await touchAccessedInner(root, ids, now);
199
+ }
200
+
201
+ async function upsertProjectLesson(root: string, lesson: HarnessLesson): Promise<HarnessLesson> {
202
+ return await upsertProjectLessonInner(root, lesson);
203
+ }
204
+
205
+ async function writeProjectLessons(root: string, lessons: HarnessLesson[]): Promise<void> {
206
+ await writeProjectLessonsInner(root, lessons);
207
+ }
208
+
209
+ async function upsertLesson(
210
+ root: string,
211
+ lesson: HarnessLesson,
212
+ tier: Exclude<LessonTier, "core">,
213
+ ): Promise<HarnessLesson> {
214
+ return await upsertLessonInner(root, lesson, tier);
215
+ }
216
+
217
+ async function creditLessons(
218
+ root: string,
219
+ ids: readonly string[],
220
+ verdict: LessonVerdict,
221
+ now?: Date,
222
+ ): Promise<void> {
223
+ await creditLessonsInner(root, ids, verdict, now);
224
+ }
225
+
226
+ export const coreFacade = {
227
+ capability: {
228
+ ENABLE_HINT,
229
+ loadCatalog,
230
+ readProjectPolicyRaw,
231
+ readRuntimeSeen,
232
+ writeRuntimeSeen,
233
+ isAvailableNotEnabled,
234
+ listAvailableNotEnabled,
235
+ listNewlyAnnounceable,
236
+ formatCapabilityDigest,
237
+ formatDoctorWarn,
238
+ formatAvailableInventory,
239
+ },
240
+ gate: {
241
+ GATE_LOCK_WAIT_MS,
242
+ GateLockTimeoutError,
243
+ writeLastGate,
244
+ readLastGate,
245
+ computeGateFingerprint,
246
+ computeInputsHash,
247
+ isCacheHit,
248
+ cachedVerdict,
249
+ gapsFromArtifact,
250
+ withGateLock,
251
+ describeHolder,
252
+ shouldAppendFiles,
253
+ appendFilesVerdict,
254
+ isRecipeRunner,
255
+ isCommandResolutionFailure,
256
+ filesFromOutput,
257
+ },
258
+ stagnation: {
259
+ computeFingerprint,
260
+ trackFingerprint,
261
+ fingerprintHits,
262
+ clearFingerprint,
263
+ recordResolution,
264
+ resolutionFor,
265
+ resolutionHistoryLine,
266
+ },
267
+ handoff: {
268
+ handoffInjectable,
269
+ patchHandoff,
270
+ readHandoff,
271
+ readHandoffFile,
272
+ readForeignSlices,
273
+ },
274
+ lesson: {
275
+ projectLessonsInjectable,
276
+ recordLessonFromFailure,
277
+ buildAuthoredLesson,
278
+ authoredLessonId,
279
+ selectLessons,
280
+ touchAccessed,
281
+ upsertProjectLesson,
282
+ upsertLesson,
283
+ writeProjectLessons,
284
+ readProjectLessons,
285
+ readGlobalLessons,
286
+ // why: the three tiers, deduped. The durable view renders from this rather than from the project store, or two
287
+ // of the three tiers never reach the host that depends on that file.
288
+ allLessons,
289
+ globalLessonsStorePath,
290
+ creditLessons,
291
+ markGradeable,
292
+ gardenAndPersistLessons,
293
+ renderLessonsMarkdown,
294
+ durableViewVerdict,
295
+ resolveSyncMode,
296
+ // why: one renderer. `src/entrypoints/support.ts` carried a copy because this was not exposed, so the tier
297
+ // added to the core block never reached the text the model actually receives
298
+ // ([/decisions/ad-040.md](/decisions/ad-040.md)).
299
+ renderLessonBlock,
300
+ promotionCount,
301
+ isInjectable,
302
+ appliesHere,
303
+ isStaleLesson,
304
+ lessonLinkVerdict,
305
+ parseLessonLink,
306
+ formatLessonLink,
307
+ validityReason,
308
+ lessonEffectiveness,
309
+ effectivenessLine,
310
+ helpRate,
311
+ },
312
+ observability: {
313
+ DEFAULT_OBS,
314
+ recordObs,
315
+ recordFromEvent,
316
+ recordAudit,
317
+ groupByProvider,
318
+ sessionReportMarkdown,
319
+ sessionReportText,
320
+ railsNeverFired,
321
+ readSignalEvents,
322
+ getRollup,
323
+ decisionsFrom,
324
+ whyText,
325
+ summarise,
326
+ NOTHING_WAS_THE_HARNESS,
327
+ pruneObs,
328
+ pruneSpool,
329
+ },
330
+ untrusted: {
331
+ clearRecall,
332
+ readRecall,
333
+ remember,
334
+ findInRecall,
335
+ recallMessage,
336
+ rememberUntrustedOutput,
337
+ askIfFromUntrusted,
338
+ evaluateUntrustedContent,
339
+ clearFramingMarker,
340
+ },
341
+ plan: {
342
+ detectPlan,
343
+ detectDeviations,
344
+ evaluatePlanGate,
345
+ planVerdict,
346
+ },
347
+ policy: {
348
+ guardPolicySurface,
349
+ checkPolicyBaseline,
350
+ policySourceFingerprint,
351
+ recordPolicyBaseline,
352
+ refreshPolicyBaselines,
353
+ acceptPolicySources,
354
+ divergedPaths,
355
+ allDivergedPaths,
356
+ activeRails,
357
+ operatorBootstrapLines,
358
+ loadPolicy,
359
+ resolveProjectPosture,
360
+ resolveProjectSyncMode,
361
+ OPERATOR_MODES,
362
+ isOperatorMode,
363
+ isUnderCodePaths,
364
+ forProvider,
365
+ },
366
+ shellPolicy: {
367
+ evaluateShellCommand,
368
+ clearShellStall,
369
+ },
370
+ subagentPolicy: {
371
+ evaluateSubagentSpawn,
372
+ upsertParentModelState,
373
+ readParentModelState,
374
+ },
375
+ supplyChain: {
376
+ isManifest,
377
+ inspectSupplyChain,
378
+ supplyChainMessage,
379
+ },
380
+ duplication: {
381
+ scanProject,
382
+ findDuplications,
383
+ duplicationMessage,
384
+ MIN_RUN,
385
+ },
386
+ commentPolicy: {
387
+ scanAddedComments,
388
+ findAddedComments,
389
+ isCommentLine,
390
+ declaresReason,
391
+ commentViolationMessage,
392
+ unknownExtensions,
393
+ KNOWN_EXTENSION_COUNT,
394
+ },
395
+ ship: {
396
+ detectShipClaim,
397
+ touchesRuntime,
398
+ recentShipClaimActive,
399
+ evaluateEmptyDiffAntiShip,
400
+ evaluateShipEvidenceGate,
401
+ appendShipLedger,
402
+ readShipLedger,
403
+ hasRecentEvidence,
404
+ newestChangeMs,
405
+ },
406
+ presence: {
407
+ register,
408
+ heartbeat,
409
+ checkCollision,
410
+ sweepStale,
411
+ release,
412
+ },
413
+ floor: {
414
+ evaluateFloor,
415
+ },
416
+ observe: {
417
+ shouldObserve,
418
+ observeAttrs,
419
+ OBSERVABLE_RAILS,
420
+ isObservableRail,
421
+ unobservableRails,
422
+ },
423
+ release: {
424
+ readDecision,
425
+ readDecisions,
426
+ allDecisionFiles,
427
+ needsAction,
428
+ formatDecisionDigest,
429
+ readReleaseSeen,
430
+ writeReleaseSeen,
431
+ },
432
+ attest: {
433
+ appendAttestation,
434
+ readAttestations,
435
+ verifyChain,
436
+ attestationPath,
437
+ fingerprintOf,
438
+ },
439
+ turn: {
440
+ readTurnActivity,
441
+ endedWithoutActing,
442
+ idleTurnMessage,
443
+ currentLoopCount,
444
+ nextLoop,
445
+ resetLoop,
446
+ checkLoopCap,
447
+ effectiveLoopCount,
448
+ markBooted,
449
+ resolveAutopilot,
450
+ formatAutopilotBlock,
451
+ classifyGateFailure,
452
+ suggestionFor,
453
+ buildGaps,
454
+ formatCarriedGaps,
455
+ formatGapFeedback,
456
+ mergeGaps,
457
+ formatProgressiveContext,
458
+ },
459
+ };
460
+
461
+ export type CoreFacade = typeof coreFacade;
@@ -0,0 +1,219 @@
1
+ import type { AddedLine } from "../../platform/git.ts";
2
+ import { matchesSyntax } from "../comment-policy/comment-policy.service.ts";
3
+ import { syntaxFor } from "../comment-policy/comment-syntax.store.ts";
4
+
5
+ /**
6
+ * A run of lines this turn added that already exists somewhere else in the project.
7
+ *
8
+ * why: the harness watches for the agent solving a problem the codebase already solved — the failure an operator
9
+ * notices weeks later as two copies drifting apart. It is a diff-scoped question, like the comment gate: only
10
+ * what this turn wrote is judged, and what was already duplicated is not this turn's to answer for
11
+ * ([/decisions/ad-071.md](/decisions/ad-071.md)).
12
+ *
13
+ * invariant: lines, never syntax. Nothing here parses a language, so the rail behaves the same in Go, Python and
14
+ * TypeScript — the constraint the comment syntax catalog exists to satisfy
15
+ * ([/decisions/ad-058.md](/decisions/ad-058.md)).
16
+ */
17
+ export type Duplication = {
18
+ file: string;
19
+ line: number;
20
+ /** Where the same run already lives. */
21
+ matchFile: string;
22
+ matchLine: number;
23
+ runLength: number;
24
+ };
25
+
26
+ /**
27
+ * hazard: below six lines the matches are punctuation. Measured on this repository at a four-line window, the
28
+ * top hits were import blocks and `} catch { return null; }` — true duplicates, and none of them a defect. Six
29
+ * is the length at which a match started being something worth reading.
30
+ */
31
+ export const MIN_RUN = 6;
32
+
33
+ /** why: a line that is only a brace, a bracket or a keyword carries no design, so it neither starts nor extends a run. */
34
+ export const MIN_LINE_CHARS = 8;
35
+
36
+ /**
37
+ * why: whitespace and the trailing comma are the two things a paste changes without changing the code, so the
38
+ * comparison ignores both. Nothing else is rewritten: renaming an identifier makes it a different line, on
39
+ * purpose, because a rail that matched through renames would report every similar-shaped function.
40
+ */
41
+ export function normaliseLine(text: string): string | null {
42
+ const collapsed = text.trim().replace(/\s+/g, " ").replace(/,$/, "");
43
+ return collapsed.length < MIN_LINE_CHARS ? null : collapsed;
44
+ }
45
+
46
+ /**
47
+ * hazard: the first calibration run reported 137 duplications at a six-line window and the top of the list was
48
+ * every import block in the repository — `import assert from "node:assert/strict"` next to `import { test } from
49
+ * "node:test"` is identical in every test file and is not a defect. The words below open a dependency
50
+ * declaration in most languages, which keeps this a vocabulary rule rather than a parser.
51
+ */
52
+ const DEPENDENCY_LINE = /^\s*(?:import|from|export|require|#include|use|using|package|namespace|open)\b/;
53
+
54
+ /** invariant: a comment is not code. Two identical licence headers are not a duplicated implementation. */
55
+ export function isCodeLine(text: string, file: string): boolean {
56
+ if (DEPENDENCY_LINE.test(text)) {
57
+ return false;
58
+ }
59
+ const syntax = syntaxFor(file);
60
+ return syntax === null ? true : !matchesSyntax(text, syntax);
61
+ }
62
+
63
+ /**
64
+ * hazard: with dependency lines excluded the calibration still reported 116 runs, and reading the top of the list
65
+ * showed every one of them was *data*: a re-export name list, a config object literal, a type's field
66
+ * declarations, a test fixture. Repeated shape is what those are for. A run has to carry operations — a call, an
67
+ * assignment, a branch, a return — before two copies of it mean anything, and a majority of the run has to carry
68
+ * them or one `foo()` inside a literal would qualify it.
69
+ */
70
+ const OPERATIONAL = /[(=]|\b(?:if|for|while|switch|return|throw|await|new|catch)\b/;
71
+
72
+ export const MIN_OPERATIONAL_RATIO = 0.5;
73
+
74
+ function operationalEnough(window: readonly { key: string }[]): boolean {
75
+ const operations = window.filter((entry) => OPERATIONAL.test(entry.key)).length;
76
+ return operations >= Math.ceil(window.length * MIN_OPERATIONAL_RATIO);
77
+ }
78
+
79
+ export type SourceLine = { file: string; line: number; text: string };
80
+
81
+ export type RunSite = { file: string; line: number };
82
+
83
+ /**
84
+ * hazard: one site per run was not enough. The project index contains the lines this turn added, so a run whose
85
+ * first occurrence happened to be the new copy compared equal to itself and every duplication vanished — the
86
+ * calibration reported zero at every window length, which is what exposed it. Two sites are the minimum that can
87
+ * answer "is there a copy somewhere other than here".
88
+ */
89
+ export const SITES_PER_RUN = 2;
90
+
91
+ export type RunIndex = Map<string, RunSite[]>;
92
+
93
+ function runKey(window: readonly string[]): string {
94
+ return window.join("\n");
95
+ }
96
+
97
+ /**
98
+ * why: one entry per distinct run, keyed by its text. The first place a run is seen wins, so a report names one
99
+ * prior home rather than every copy — an operator asked to look at eleven identical sites reads none of them.
100
+ */
101
+ export function indexRuns(lines: readonly SourceLine[], minRun = MIN_RUN): RunIndex {
102
+ const index: RunIndex = new Map();
103
+ const usable = lines
104
+ .map((entry) => ({
105
+ ...entry,
106
+ key: isCodeLine(entry.text, entry.file) ? normaliseLine(entry.text) : null,
107
+ }))
108
+ .map((entry) => (entry.key === null ? null : { file: entry.file, line: entry.line, key: entry.key }));
109
+
110
+ for (let start = 0; start + minRun <= usable.length; start += 1) {
111
+ const window = usable.slice(start, start + minRun);
112
+ // hazard: a run has to be contiguous in the file as well as in this array, or a match spans a gap the reader
113
+ // cannot see. Two files concatenated into one array would otherwise produce a run that exists nowhere.
114
+ if (window.some((entry) => entry === null)) {
115
+ continue;
116
+ }
117
+ const solid = window as { file: string; line: number; key: string }[];
118
+ const head = solid[0] as { file: string; line: number; key: string };
119
+ if (solid.some((entry, offset) => entry.file !== head.file || entry.line !== head.line + offset)) {
120
+ continue;
121
+ }
122
+ if (!operationalEnough(solid)) {
123
+ continue;
124
+ }
125
+ const key = runKey(solid.map((entry) => entry.key));
126
+ const sites = index.get(key) ?? [];
127
+ if (sites.length < SITES_PER_RUN) {
128
+ index.set(key, [...sites, { file: head.file, line: head.line }]);
129
+ }
130
+ }
131
+ return index;
132
+ }
133
+
134
+ /**
135
+ * why: the added lines are indexed the same way the project is, then intersected. A run this turn added that the
136
+ * project already had, somewhere the turn did not write, is the finding.
137
+ */
138
+ export function findDuplications(
139
+ added: readonly AddedLine[],
140
+ project: RunIndex,
141
+ minRun = MIN_RUN,
142
+ ): Duplication[] {
143
+ const found: Duplication[] = [];
144
+ const reported = new Set<string>();
145
+ for (const [key, sites] of indexRuns(added, minRun)) {
146
+ const where = sites[0] as RunSite;
147
+ // invariant: a run matching itself is not a duplication, and the project index contains the very lines this
148
+ // turn added — so the answer is the first site that is somewhere else.
149
+ const prior = (project.get(key) ?? []).find(
150
+ (site) => site.file !== where.file || site.line !== where.line,
151
+ );
152
+ if (prior === undefined) {
153
+ continue;
154
+ }
155
+ const seen = `${where.file}:${where.line}`;
156
+ if (reported.has(seen)) {
157
+ continue;
158
+ }
159
+ reported.add(seen);
160
+ found.push({
161
+ file: where.file,
162
+ line: where.line,
163
+ matchFile: prior.file,
164
+ matchLine: prior.line,
165
+ runLength: minRun,
166
+ });
167
+ }
168
+ return found.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
169
+ }
170
+
171
+ export function duplicationMessage(hits: readonly Duplication[]): string {
172
+ return [
173
+ `BLOCKED: this turn added ${hits.length} run(s) of ${MIN_RUN}+ lines that already exist in this project.`,
174
+ "TRIED: compared the lines this turn added against the rest of the repository, ignoring",
175
+ "comments, blank lines and whitespace. A run already duplicated before this turn is not counted.",
176
+ "NEED: call the existing code, or extract what both need. If the duplication is deliberate —",
177
+ "the two will diverge, or the shared form would couple them — say which, in one line, and continue.",
178
+ "",
179
+ ...hits
180
+ .slice(0, 10)
181
+ .map((hit) => `${hit.file}:${hit.line} already at ${hit.matchFile}:${hit.matchLine}`),
182
+ ].join("\n");
183
+ }
184
+
185
+ /**
186
+ * hazard: hook latency is a product property ([/decisions/ad-012.md](/decisions/ad-012.md)), and the honest
187
+ * version of this scan reads every tracked file on every stop. These bounds are what keep a large repository
188
+ * from paying for the rail in wall-clock; the report says when one was reached, because a scan that silently
189
+ * covered half a project reads as a clean answer.
190
+ */
191
+ export const MAX_SCAN_FILES = 2000;
192
+ export const MAX_SCAN_BYTES = 8_000_000;
193
+
194
+ export type ProjectScan = { index: RunIndex; filesRead: number; truncated: boolean };
195
+
196
+ export type ReadFile = (relativePath: string) => string | null;
197
+
198
+ export function scanProject(files: readonly string[], readFile: ReadFile, minRun = MIN_RUN): ProjectScan {
199
+ const lines: SourceLine[] = [];
200
+ let bytes = 0;
201
+ let filesRead = 0;
202
+ let truncated = false;
203
+ for (const file of files) {
204
+ if (filesRead >= MAX_SCAN_FILES || bytes >= MAX_SCAN_BYTES) {
205
+ truncated = true;
206
+ break;
207
+ }
208
+ const text = readFile(file);
209
+ if (text === null) {
210
+ continue;
211
+ }
212
+ bytes += text.length;
213
+ filesRead += 1;
214
+ for (const [index, line] of text.split("\n").entries()) {
215
+ lines.push({ file, line: index + 1, text: line });
216
+ }
217
+ }
218
+ return { index: indexRuns(lines, minRun), filesRead, truncated };
219
+ }