@joshuafolkken/kit 1.1005.0 → 1.1035.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 (272) hide show
  1. package/.claude/settings.json +1 -5
  2. package/.claude/skills/dependency-update/SKILL.md +6 -3
  3. package/.claude/skills/epic-commands/SKILL.md +46 -17
  4. package/.claude/skills/workflow-commands/SKILL.md +8 -23
  5. package/.claude/skills/workflow-commands/backlogrun-child.md +4 -2
  6. package/.claude/skills/workflow-commands/backlogrun.md +3 -12
  7. package/.claude/skills/workflow-commands/fullrun-steps.md +4 -3
  8. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  9. package/.claude/skills/workflow-commands/halfrun.md +2 -1
  10. package/.claude/skills/workflow-commands/into-target.md +1 -1
  11. package/.claude/skills/workflow-commands/observation-filing.md +42 -43
  12. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  13. package/.codex/hooks.json +1 -5
  14. package/.github/workflows/sonar-qube.yml +1 -1
  15. package/AGENTS.md +4 -25
  16. package/CLAUDE.md +24 -38
  17. package/GEMINI.md +4 -24
  18. package/README.md +22 -58
  19. package/dist/CLAUDE.md +24 -38
  20. package/dist/hooks/batch-guard.js +4 -4
  21. package/dist/hooks/{chunk-WMIR5SNX.js → chunk-D67KVGGY.js} +3 -3
  22. package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-EIFCAJRB.js} +70 -27
  23. package/dist/hooks/{chunk-KPZN42RT.js → chunk-GJT4BNVE.js} +3 -3
  24. package/dist/hooks/{chunk-PAXMLGSS.js → chunk-GLBDBJOK.js} +44 -24
  25. package/dist/hooks/{chunk-GBJUERYK.js → chunk-HOYJU5RM.js} +1 -1
  26. package/dist/hooks/{chunk-6PSTVOLH.js → chunk-HU66PWWL.js} +33 -57
  27. package/dist/hooks/{chunk-A2H5I2VD.js → chunk-KG6PZD72.js} +2 -2
  28. package/dist/hooks/{chunk-AWTFKKFW.js → chunk-XPLP27WA.js} +3 -8
  29. package/dist/hooks/{chunk-4GDR2YTV.js → chunk-Y27M6A7X.js} +4 -4
  30. package/dist/hooks/{chunk-56LGMJJV.js → chunk-ZXNE6NY3.js} +2 -2
  31. package/dist/hooks/codex-hook-adapter.js +10 -10
  32. package/dist/hooks/duplicate-read-guard.js +5 -5
  33. package/dist/hooks/format-edited.js +2 -2
  34. package/dist/hooks/investigation-guard.js +5 -5
  35. package/dist/hooks/pretool-guard.js +9 -9
  36. package/dist/hooks/stop-guard.js +11 -15
  37. package/dist/josh.js +38 -19
  38. package/dist/version/index.js +93 -41
  39. package/eslint/base.js +2 -11
  40. package/package.json +32 -6
  41. package/prompts/coding-standards.md +15 -290
  42. package/prompts/collaboration-workflow/cross-repo-epic.md +2 -51
  43. package/prompts/collaboration-workflow/epic-audit.md +2 -53
  44. package/prompts/collaboration-workflow/epic-bundle.md +2 -106
  45. package/prompts/collaboration-workflow/issue-template.md +4 -4
  46. package/prompts/collaboration-workflow/operating-rules.md +18 -75
  47. package/prompts/collaboration-workflow/principles.md +74 -0
  48. package/prompts/collaboration-workflow/residency.md +54 -14
  49. package/prompts/collaboration-workflow/rule-delivery.md +1 -11
  50. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  51. package/prompts/collaboration-workflow.md +22 -28
  52. package/scripts/adopt/adopt-logic.ts +2 -2
  53. package/scripts/adopt/adopt-toolkits.ts +1 -1
  54. package/scripts/adopt/adopt.ts +2 -2
  55. package/scripts/agent/agent-event.ts +2 -9
  56. package/scripts/agent/agent-exit-record.ts +2 -9
  57. package/scripts/backlog/backlog-budget-cli.ts +2 -6
  58. package/scripts/backlog/backlog-drive-cli.ts +20 -10
  59. package/scripts/backlog/backlog-drive.ts +50 -16
  60. package/scripts/backlog/backlog-offer-cli.ts +6 -5
  61. package/scripts/backlog/backlog-plan.ts +1 -1
  62. package/scripts/build/build-bin.ts +2 -1
  63. package/scripts/build/build-config-merge.ts +2 -1
  64. package/scripts/build/build-hooks.ts +24 -4
  65. package/scripts/build/build-managed-marker.ts +2 -1
  66. package/scripts/build/build-self-sync-guard.ts +2 -1
  67. package/scripts/build/build-version.ts +2 -1
  68. package/scripts/cases/cases-logic.ts +2 -6
  69. package/scripts/ci/pr-classification.ts +13 -11
  70. package/scripts/cost-runtime/cost-transcript.ts +4 -11
  71. package/scripts/doctor/doctor-consumer.ts +5 -12
  72. package/scripts/doctor/doctor-ports.ts +2 -2
  73. package/scripts/document/document-byte-budget.ts +8 -16
  74. package/scripts/document/document-section.ts +2 -6
  75. package/scripts/document/entry-read-budget.ts +1 -1
  76. package/scripts/{git/git-epic-add-body.ts → epic/epic-add-body.ts} +23 -23
  77. package/scripts/{git/git-epic-add-plan.ts → epic/epic-add-plan.ts} +23 -26
  78. package/scripts/{git/git-epic-add.ts → epic/epic-add.ts} +17 -17
  79. package/scripts/epic/epic-audit-cli.ts +4 -4
  80. package/scripts/epic/epic-audit-orphans.ts +2 -2
  81. package/scripts/epic/epic-audit-rationale.ts +1 -1
  82. package/scripts/epic/epic-audit.ts +1 -1
  83. package/scripts/{git/git-epic-body.ts → epic/epic-body.ts} +6 -6
  84. package/scripts/epic/epic-bundle-cli.ts +1 -1
  85. package/scripts/epic/epic-bundle.ts +2 -2
  86. package/scripts/epic/epic-candidate-confirm.ts +2 -1
  87. package/scripts/{git/git-epic-chains.ts → epic/epic-chains.ts} +4 -4
  88. package/{scripts-ai → scripts/epic}/epic-check.ts +5 -4
  89. package/{scripts-ai → scripts/epic}/epic-cli.ts +4 -4
  90. package/scripts/{git/git-epic-close-comment.ts → epic/epic-close-comment.ts} +9 -9
  91. package/scripts/{git/git-epic-close.ts → epic/epic-close.ts} +23 -19
  92. package/scripts/epic/epic-cross-repo.ts +21 -38
  93. package/scripts/{git/git-epic-decision.ts → epic/epic-decision.ts} +12 -14
  94. package/scripts/epic/epic-fetch.ts +3 -3
  95. package/scripts/epic/epic-graph.ts +3 -3
  96. package/scripts/epic/epic-index.ts +2 -2
  97. package/scripts/epic/epic-issue.ts +1 -1
  98. package/scripts/epic/epic-next.ts +3 -3
  99. package/scripts/{git/git-epic-parse.ts → epic/epic-parse.ts} +3 -3
  100. package/scripts/{git/git-epic-promote.ts → epic/epic-promote.ts} +10 -10
  101. package/scripts/{git/git-epic-read.ts → epic/epic-read.ts} +9 -9
  102. package/scripts/{git/git-epic-reconcile.ts → epic/epic-reconcile.ts} +18 -20
  103. package/scripts/{git/git-epic-reference.ts → epic/epic-reference.ts} +4 -23
  104. package/scripts/epic/epic-relation-recheck.ts +4 -3
  105. package/scripts/{git/git-epic-relations.ts → epic/epic-relations.ts} +5 -5
  106. package/scripts/{git/git-epic-remove-plan.ts → epic/epic-remove-plan.ts} +17 -19
  107. package/scripts/{git/git-epic-remove.ts → epic/epic-remove.ts} +13 -13
  108. package/scripts/{git/git-epic-run.ts → epic/epic-run.ts} +27 -27
  109. package/scripts/{git/git-epic-sections.ts → epic/epic-sections.ts} +6 -6
  110. package/scripts/{git/git-epic-shape.ts → epic/epic-shape.ts} +7 -7
  111. package/scripts/{git/git-epic-validate.ts → epic/epic-validate.ts} +13 -13
  112. package/{scripts-ai → scripts/epic}/epic.ts +17 -16
  113. package/scripts/gh/fix-gh-packages.ts +2 -1
  114. package/scripts/gh/gh-spawn.ts +12 -10
  115. package/scripts/git/constants.ts +1 -1
  116. package/scripts/git/git-branch.ts +10 -1
  117. package/scripts/git/git-command.ts +1 -1
  118. package/scripts/git/git-error.ts +2 -5
  119. package/{scripts-ai → scripts/git}/git-followup-finish.ts +7 -7
  120. package/scripts/git/git-followup-flush.ts +74 -48
  121. package/scripts/git/git-followup-issue-close.ts +2 -2
  122. package/scripts/git/git-followup-label.ts +1 -1
  123. package/scripts/git/git-followup-pending.ts +1 -1
  124. package/{scripts-ai → scripts/git}/git-followup-workflow.ts +23 -11
  125. package/scripts/git/git-gh-exec.ts +85 -26
  126. package/scripts/git/git-gh-issue-read.ts +2 -2
  127. package/scripts/git/git-gh-issue-rest.ts +2 -2
  128. package/scripts/git/git-gh-issue-write.ts +6 -6
  129. package/scripts/git/git-gh-pr-read.ts +0 -21
  130. package/scripts/git/git-gh-pr.ts +2 -23
  131. package/scripts/git/git-pr-followup-wrapup.ts +2 -6
  132. package/scripts/git/git-preflight.ts +3 -6
  133. package/scripts/git/git-staging.ts +21 -9
  134. package/{scripts-ai → scripts/git}/git-workflow.ts +11 -11
  135. package/scripts/git/github-issue-url.ts +1 -1
  136. package/scripts/git/issue-labels.ts +1 -1
  137. package/scripts/git/issue-reference.ts +19 -0
  138. package/scripts/git/main-merge.ts +2 -1
  139. package/scripts/git/main-sync.ts +3 -2
  140. package/scripts/git/schemas.ts +1 -1
  141. package/scripts/git/stash-pop-args.ts +7 -13
  142. package/scripts/git/stash-pop-cli.ts +2 -1
  143. package/{scripts-ai → scripts/git}/telegram-test-logic.ts +2 -2
  144. package/{scripts-ai → scripts/git}/telegram-test.ts +7 -7
  145. package/scripts/hooks/format-rewrite-notice.ts +2 -8
  146. package/scripts/init/init-ai-copy.ts +23 -14
  147. package/scripts/init/init.ts +8 -4
  148. package/scripts/init/start-steps.ts +2 -1
  149. package/scripts/init/start.ts +2 -1
  150. package/scripts/issue/issue-comment-cli.ts +2 -1
  151. package/scripts/issue/issue-file-cli.ts +2 -1
  152. package/scripts/issue/issue-fold-cli.ts +9 -16
  153. package/scripts/issue/issue-scout-cli.ts +8 -13
  154. package/scripts/issue/measure-rerun-cli.ts +7 -6
  155. package/scripts/josh/josh-commands-ai.ts +2 -2
  156. package/scripts/josh/josh-commands-backlog.ts +1 -1
  157. package/scripts/josh/josh-commands-development.ts +1 -1
  158. package/scripts/josh/josh-commands-workflow.ts +4 -4
  159. package/scripts/lane/lane-cli.ts +2 -1
  160. package/scripts/lane/lane-open.ts +3 -2
  161. package/scripts/lane/lane-registry.ts +3 -6
  162. package/scripts/lib/cli-flags.ts +20 -2
  163. package/scripts/lib/error-message.ts +9 -0
  164. package/scripts/lib/read-file.ts +26 -2
  165. package/scripts/observations/observation-ledger-home.ts +98 -26
  166. package/scripts/observations/observation-ledger-migrate.ts +26 -21
  167. package/scripts/observations/observation-ledger-prepare.ts +10 -20
  168. package/scripts/observations/observation-ledger.ts +58 -25
  169. package/scripts/observations/observations-flush-cli.ts +6 -23
  170. package/scripts/observations/observations-flush-landing.ts +2 -1
  171. package/scripts/observations/observations-flush.ts +29 -20
  172. package/scripts/package/package-scout-cli.ts +8 -16
  173. package/scripts/ports/port-command.ts +2 -1
  174. package/scripts/propagate/propagate-publish.ts +6 -7
  175. package/scripts/propagate/propagate-steps.ts +2 -1
  176. package/scripts/propagate/propagate.ts +3 -2
  177. package/scripts/refactor/import-graph.ts +2 -6
  178. package/scripts/registry-migration/migrate.ts +3 -2
  179. package/scripts/registry-migration/user-npmrc.ts +2 -2
  180. package/scripts/release/release-cli.ts +2 -1
  181. package/scripts/repo/repo-setting.ts +10 -8
  182. package/scripts/review/review-finding-ledger.ts +3 -3
  183. package/scripts/review/review-findings-cli.ts +4 -11
  184. package/scripts/review/review-record-cli.ts +14 -11
  185. package/scripts/review/review-record.ts +6 -16
  186. package/scripts/rules/decision-oracle.ts +2 -2
  187. package/scripts/rules/rule-body-guard.ts +2 -2
  188. package/scripts/rules/rule-value-cache.ts +99 -0
  189. package/scripts/rules/rule-value-cli.ts +10 -9
  190. package/scripts/rules/rule-value.ts +1 -1
  191. package/scripts/run/run-carry-args.ts +2 -6
  192. package/scripts/run/run-carry-cli.ts +5 -7
  193. package/scripts/run/run-cut-args.ts +7 -13
  194. package/scripts/run/run-ending-cli.ts +4 -7
  195. package/scripts/run/run-event-stream.ts +2 -5
  196. package/scripts/run/run-liveness-cli.ts +4 -7
  197. package/scripts/run/run-progress-args.ts +2 -6
  198. package/scripts/run/run-progress-cli.ts +2 -1
  199. package/scripts/run/run-tail-cli.ts +3 -3
  200. package/scripts/run/run-tidy-stashes.ts +7 -14
  201. package/scripts/run/run-wake-cli.ts +2 -6
  202. package/scripts/run/run-wake-driver.ts +15 -2
  203. package/scripts/run/run-wake-session.ts +14 -4
  204. package/scripts/safe-chain/project-config.ts +3 -6
  205. package/scripts/security/security-audit-provision.ts +6 -7
  206. package/scripts/sonar/sonar-hotspots-cli.ts +2 -1
  207. package/scripts/sync/directory-copy-guard.ts +2 -1
  208. package/scripts/sync/sync-ai-files.ts +3 -3
  209. package/scripts/sync/template-source-logic.ts +3 -4
  210. package/scripts/test/e2e-retry-check.ts +3 -6
  211. package/scripts/ui/ui-routes-cli.ts +3 -6
  212. package/scripts/version/bump-version.ts +2 -1
  213. package/scripts/version/version-remote.ts +18 -27
  214. package/scripts/version/version-targets.ts +3 -3
  215. package/.claude/skills/workflow-commands/rule-residency.md +0 -267
  216. package/evals/scenarios/consult-not-execute.json +0 -20
  217. package/evals/scenarios/no-implicit-workflow.json +0 -26
  218. package/evals/scenarios/overrides-both-locations.json +0 -29
  219. package/evals/scenarios/upstream-interrupt.json +0 -23
  220. package/evals/scenarios/workflow-skill-loaded.json +0 -16
  221. package/prompts/agent-lint-protocol.json +0 -79
  222. package/prompts/agent-rules.md +0 -63
  223. package/prompts/collaboration-workflow/consultation-vs-execution.md +0 -8
  224. package/prompts/collaboration-workflow/distributed-docs.md +0 -7
  225. package/prompts/collaboration-workflow/durable-rules.md +0 -24
  226. package/prompts/collaboration-workflow/latest-first.md +0 -14
  227. package/prompts/collaboration-workflow/no-clones.md +0 -11
  228. package/prompts/collaboration-workflow/simplicity-first.md +0 -9
  229. package/prompts/collaboration-workflow/single-source-rules.md +0 -13
  230. package/scripts/build/shim-shell.ts +0 -25
  231. package/scripts/claude/skill-meta.ts +0 -37
  232. package/scripts/document/document-scan.ts +0 -108
  233. package/scripts/document/generate-catalog.ts +0 -69
  234. package/scripts/document/retired-phrases.ts +0 -143
  235. package/scripts/document/review-agent-type.ts +0 -46
  236. package/scripts/document/single-source.ts +0 -73
  237. package/scripts/git/git-fixture-workspace.ts +0 -88
  238. package/scripts/hooks/format-edited-file-fixtures.ts +0 -109
  239. package/scripts/hooks/prompt-hooks.ts +0 -38
  240. package/scripts/init/hook-command-bootstrap.ts +0 -83
  241. package/scripts/issue/issue-logic.ts +0 -50
  242. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +0 -1227
  243. package/scripts/josh/josh-script-reader.ts +0 -11
  244. package/scripts/package/production-dependency-graph.ts +0 -68
  245. package/scripts/retrospective/retrospective-cli.ts +0 -97
  246. package/scripts/retrospective/retrospective.ts +0 -127
  247. package/scripts/rules/delivered-rules-harness.ts +0 -72
  248. package/scripts/rules/top-level-scripts-guard.ts +0 -47
  249. package/scripts/run/run-carry-flush.ts +0 -58
  250. package/scripts/run/run-transcript.golden.txt +0 -57
  251. package/scripts/run/run-wake-work.ts +0 -108
  252. package/scripts/sync/dependabot-pin-logic.ts +0 -135
  253. package/scripts/test/classify-isolation.ts +0 -149
  254. package/scripts/test/http-fault-injection.ts +0 -79
  255. package/scripts/test/josh-harness-environment.ts +0 -260
  256. package/scripts/test/josh-harness.ts +0 -96
  257. package/scripts/test/pilot-files.ts +0 -452
  258. package/scripts/test/pilot-measure.ts +0 -78
  259. package/scripts/test/test-network-guard.ts +0 -453
  260. package/scripts/test/test-repository-guard.ts +0 -317
  261. package/scripts/test/test-state-guard.ts +0 -85
  262. package/scripts/test/test-stdout-guard.ts +0 -54
  263. package/scripts/test/test-telegram-guard.ts +0 -90
  264. package/scripts/test/unit-guard-environment.ts +0 -12
  265. package/scripts/test/unit-projects.ts +0 -139
  266. package/scripts/test/vitest-include-globs.ts +0 -12
  267. package/scripts/time-runtime/time-batch-guard-fixtures.ts +0 -66
  268. package/scripts-ai/completion-gate.ts +0 -13
  269. package/scripts-ai/environment-loader.ts +0 -9
  270. /package/{scripts-ai → scripts/epic}/epic-cli-argv.ts +0 -0
  271. /package/{scripts-ai → scripts/epic}/epic-cli-remove.ts +0 -0
  272. /package/{scripts-ai → scripts/git}/followup-issue-number.ts +0 -0
@@ -1,11 +0,0 @@
1
- import { readFileSync } from 'node:fs'
2
- import path from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
-
5
- const REPOSITORY_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
6
-
7
- function read_script(script: string): string {
8
- return readFileSync(path.join(REPOSITORY_ROOT, script), 'utf8')
9
- }
10
-
11
- export { read_script }
@@ -1,68 +0,0 @@
1
- interface LockDependency {
2
- specifier: string
3
- version: string
4
- }
5
-
6
- interface LockSnapshot {
7
- dependencies?: Record<string, string>
8
- optionalDependencies?: Record<string, string>
9
- }
10
-
11
- interface LockImporter {
12
- dependencies: Record<string, LockDependency>
13
- optionalDependencies?: Record<string, LockDependency>
14
- }
15
-
16
- interface Lockfile {
17
- importers: Record<string, LockImporter>
18
- snapshots: Record<string, LockSnapshot>
19
- }
20
-
21
- function collect_snapshot(
22
- lockfile: Lockfile,
23
- names: Set<string>,
24
- name: string,
25
- version: string,
26
- ): void {
27
- const key = `${name}@${version}`
28
- if (names.has(key)) return
29
- const snapshot = lockfile.snapshots[key]
30
- if (!snapshot) throw new Error(`Missing production snapshot: ${key}`)
31
- names.add(key)
32
-
33
- const children = { ...snapshot.dependencies, ...snapshot.optionalDependencies }
34
-
35
- for (const [child, child_version] of Object.entries(children)) {
36
- collect_snapshot(lockfile, names, child, child_version)
37
- }
38
- }
39
-
40
- function root_version(importer: LockImporter, name: string, specifier: string): string {
41
- const entry = importer.dependencies[name] ?? importer.optionalDependencies?.[name]
42
- if (entry?.specifier !== specifier) throw new Error(`Production lockfile mismatch: ${name}`)
43
-
44
- return entry.version
45
- }
46
-
47
- function production_names(
48
- lockfile: Lockfile,
49
- dependencies: Record<string, string>,
50
- optional_dependencies: Record<string, string> = {},
51
- ): Set<string> {
52
- const seen = new Set<string>()
53
- const importer = lockfile.importers['.']
54
- if (!importer) throw new Error('Missing root importer in production lockfile')
55
-
56
- const roots = { ...dependencies, ...optional_dependencies }
57
-
58
- for (const [name, specifier] of Object.entries(roots)) {
59
- collect_snapshot(lockfile, seen, name, root_version(importer, name, specifier))
60
- }
61
-
62
- return new Set([...seen].map((key) => key.slice(0, key.lastIndexOf('@'))))
63
- }
64
-
65
- const production_dependency_graph = { names: production_names }
66
-
67
- export type { Lockfile }
68
- export { production_dependency_graph }
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import { readFile } from 'node:fs/promises'
3
- import { fileURLToPath } from 'node:url'
4
- import { cost_run_report, type RunCostReport } from '#scripts/cost/cost-run-report'
5
- import { cost_run_tree } from '#scripts/cost/cost-run-tree'
6
- import { observation_ledger_home } from '#scripts/observations/observation-ledger-home'
7
- import { observation_ledger_line } from '#scripts/observations/observation-ledger-line'
8
- import { review_finding_ledger } from '#scripts/review/review-finding-ledger'
9
- import { run_carry } from '#scripts/run/run-carry'
10
- import { run_event_scope, type EventScope } from '#scripts/run/run-event-scope'
11
- import { run_event_stream, type RunEvent } from '#scripts/run/run-event-stream'
12
- import { retrospective, type RetrospectiveInputs } from './retrospective'
13
-
14
- // `josh retrospective` — the end-of-run retrospective (joshuafolkken/kit#2328). It is the aggregation
15
- // half: it gathers the four measurements that already exist and prints the digest `retrospective.ts`
16
- // composes, so the retrospective step has one place to read the run it just finished. **When to run it
17
- // is the run driver's** — `run:step` prints it at the stop position, once per invocation — and **what to
18
- // file from it is `retrospective.md`'s**; this only reads and shapes.
19
- //
20
- // **Kit-only.** It reads kit's own development run — its transcript store, its review ledger, its event
21
- // stream — so it means nothing in a consumer project and is dropped from a consumer's help and refused
22
- // there, through the existing `is_kit_only` declaration rather than a new judgement.
23
-
24
- const SUCCESS_EXIT_CODE = 0
25
- // `read_from` returns every event past the given position, so zero is "the whole stream from the start".
26
- const STREAM_START = 0
27
-
28
- function read_cost(cwd: string): RunCostReport | undefined {
29
- const tree = cost_run_tree.load(cwd, undefined)
30
-
31
- if (tree === undefined) return undefined
32
-
33
- return cost_run_report.build(tree.run_count, tree.unattributed_count, tree.nodes)
34
- }
35
-
36
- async function read_ledger(): Promise<string> {
37
- try {
38
- return await readFile(observation_ledger_home.ledger_path(), 'utf8')
39
- } catch {
40
- return ''
41
- }
42
- }
43
-
44
- function ledger_entries(content: string): Array<string> {
45
- return content.split('\n').filter((line) => observation_ledger_line.is_ledger_entry_line(line))
46
- }
47
-
48
- interface RunRead {
49
- events: ReadonlyArray<RunEvent>
50
- scope: EventScope
51
- }
52
-
53
- // The stream and the scope that bounds it, read from the one directory the carry record and the event stream
54
- // share (joshuafolkken/kit#2395). The whole stream is read — `scoped_events` filters it to this invocation —
55
- // and an unresolvable directory has no record to scope with, which is the undetermined answer an absent
56
- // record gives.
57
- async function read_run(): Promise<RunRead> {
58
- const directory = await run_carry.repository_directory()
59
-
60
- if (directory === undefined) return { events: [], scope: run_event_scope.UNKNOWN_EVENT_SCOPE }
61
-
62
- const target = run_event_stream.target_of(directory)
63
- const { events } = run_event_stream.read_from(target, STREAM_START)
64
- const scope = run_event_scope.scope_of(run_carry.read_carry(run_carry.carry_path(directory)))
65
-
66
- return { events, scope }
67
- }
68
-
69
- async function gather(cwd: string): Promise<RetrospectiveInputs> {
70
- const ledger = await read_ledger()
71
- const run_read = await read_run()
72
-
73
- return {
74
- cost: read_cost(cwd),
75
- findings: review_finding_ledger.category_counts(ledger),
76
- zero_rounds: review_finding_ledger.zero_round_count(ledger),
77
- observations: ledger_entries(ledger),
78
- events: run_read.events,
79
- scope: run_read.scope,
80
- }
81
- }
82
-
83
- async function run(cwd: string = process.cwd()): Promise<number> {
84
- console.info(retrospective.compose(await gather(cwd)))
85
-
86
- return SUCCESS_EXIT_CODE
87
- }
88
-
89
- async function main(): Promise<void> {
90
- process.exitCode = await run()
91
- }
92
-
93
- const retrospective_cli = { gather, run, main }
94
-
95
- if (process.argv[1] === fileURLToPath(import.meta.url)) await main()
96
-
97
- export { retrospective_cli }
@@ -1,127 +0,0 @@
1
- import { cost_format } from '#scripts/cost-runtime/cost-format'
2
- import type { RunCostReport } from '#scripts/cost/cost-run-report'
3
- import type { RoleTotals } from '#scripts/cost/cost-run-roles'
4
- import type { CategoryCount } from '#scripts/review/review-finding-ledger'
5
- import { run_event_scope, type EventScope } from '#scripts/run/run-event-scope'
6
- import type { RunEvent } from '#scripts/run/run-event-stream'
7
-
8
- // The composition half of `josh retrospective` — the end-of-run retrospective's aggregation
9
- // (joshuafolkken/kit#2328). A run that drains its backlog has spent time and money that nobody reads;
10
- // this folds the four measurements that already exist — the run tree's cost and time
11
- // (`cost-run-report.ts`), the recurring review findings (`review-finding-ledger.ts`), the observation
12
- // ledger and the run's own event stream — into one digest, so the retrospective step has something to
13
- // weigh. It adds no new measurement, per the observation-filing rule against readerless numbers; every
14
- // input here is read from a command that was already keeping it.
15
- //
16
- // **It is a pure function of the four inputs.** The gathering — loading the run tree, reading the
17
- // ledger, opening the stream — is the CLI's; this shapes what they return and nothing else, so the
18
- // digest is unit-tested against fixed inputs rather than against a live store.
19
-
20
- const MS_PER_MINUTE = 60_000
21
- const PERCENT = 100
22
- // A role whose share of the run's wall clock runs this far ahead of its share of the cost is time the
23
- // run spent waiting rather than working — the disparity the retrospective exists to surface (the wake
24
- // role in the baseline sat at 52% of the time for 25% of the cost, a gap of 0.27).
25
- const WAITING_GAP = 0.2
26
-
27
- // The friction each event kind marks — a place the run did not run straight through — counted back
28
- // over the whole run. A plan, a child launch, a merge and a PR opening are the run working as intended,
29
- // so they are not friction and are left out.
30
- const FRICTION_KINDS = ['park', 'outage', 'cut', 'review-round'] as const
31
-
32
- interface RetrospectiveInputs {
33
- cost: RunCostReport | undefined
34
- findings: ReadonlyArray<CategoryCount>
35
- zero_rounds: number
36
- // The observation ledger's entry lines, already filtered to entries by the caller.
37
- observations: ReadonlyArray<string>
38
- events: ReadonlyArray<RunEvent>
39
- // Which invocation the friction count covers (joshuafolkken/kit#2395). The stream is the repository's
40
- // event log, not this run's, so a digest handed the whole of it would report a previous invocation's
41
- // cut and park as this run's friction — the very numbers the retrospective weighs to file improvement
42
- // issues. The scope is `run-event-scope.ts`'s, shared with `run:report` and `run:step`.
43
- scope: EventScope
44
- }
45
-
46
- const CLOSING =
47
- 'Weigh these against retrospective.md: file the improvements worth carrying into the next run, and stack the rest in the observation ledger.'
48
-
49
- function minutes(ms: number): string {
50
- return String(Math.round(ms / MS_PER_MINUTE))
51
- }
52
-
53
- function percent(share: number): string {
54
- return `${String(Math.round(share * PERCENT))}%`
55
- }
56
-
57
- // A role's time share running ahead of its cost share is the run waiting rather than working, so it is
58
- // called out; a role that pays its way carries no note.
59
- function waiting_note(role: RoleTotals): string {
60
- return role.elapsed_share - role.cost_share >= WAITING_GAP ? ' — waiting-heavy' : ''
61
- }
62
-
63
- function role_line(role: RoleTotals): string {
64
- const spent = `${percent(role.cost_share)} cost, ${percent(role.elapsed_share)} time`
65
-
66
- return ` ${role.role}: ${spent}${waiting_note(role)}`
67
- }
68
-
69
- function cost_lines(cost: RunCostReport | undefined): Array<string> {
70
- if (cost === undefined) return ['Run cost & time: no run transcripts found']
71
-
72
- const totals = `${String(cost.session_count)} session(s), ${minutes(cost.total_elapsed_ms)} min, ${cost_format.format_usd(cost.total_usd)}`
73
-
74
- return [`Run cost & time: ${totals}`, ...cost.roles.map((role) => role_line(role))]
75
- }
76
-
77
- function findings_lines(
78
- findings: ReadonlyArray<CategoryCount>,
79
- zero_rounds: number,
80
- ): Array<string> {
81
- const denominator = `${String(zero_rounds)} zero-finding round(s)`
82
-
83
- if (findings.length === 0) return [`Review findings: none recorded (${denominator})`]
84
-
85
- const body = findings.map((entry) => ` ${entry.category}: ${String(entry.count)}`)
86
-
87
- return [`Review findings (${denominator}):`, ...body]
88
- }
89
-
90
- function observation_lines(observations: ReadonlyArray<string>): Array<string> {
91
- return [`Observation ledger: ${String(observations.length)} entry(ies) held`]
92
- }
93
-
94
- function friction_count(events: ReadonlyArray<RunEvent>, kind: string): number {
95
- return events.filter((event) => event.kind === kind).length
96
- }
97
-
98
- // This invocation's friction, counted over its own events only. An undetermined scope falls to an empty
99
- // list rather than the whole stream (joshuafolkken/kit#2395): counting every invocation's friction as this
100
- // run's is the defect, so the safe fall is to attribute nothing that cannot be attributed.
101
- function friction_lines(events: ReadonlyArray<RunEvent>, scope: EventScope): Array<string> {
102
- const scoped = run_event_scope.scoped_events(events, scope) ?? []
103
- const counts = FRICTION_KINDS.map((kind) => `${kind} ${String(friction_count(scoped, kind))}`)
104
-
105
- return [`Run events: ${counts.join(', ')}`]
106
- }
107
-
108
- // The four sections in a fixed order, separated by blank lines, closed by the pointer that hands the
109
- // judgement — which improvements are worth filing — to `retrospective.md`.
110
- function compose(inputs: RetrospectiveInputs): string {
111
- return [
112
- ...cost_lines(inputs.cost),
113
- '',
114
- ...findings_lines(inputs.findings, inputs.zero_rounds),
115
- '',
116
- ...observation_lines(inputs.observations),
117
- '',
118
- ...friction_lines(inputs.events, inputs.scope),
119
- '',
120
- CLOSING,
121
- ].join('\n')
122
- }
123
-
124
- const retrospective = { CLOSING, FRICTION_KINDS, WAITING_GAP, compose }
125
-
126
- export type { RetrospectiveInputs }
127
- export { retrospective }
@@ -1,72 +0,0 @@
1
- import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
2
- import { tmpdir } from 'node:os'
3
- import path from 'node:path'
4
- import { delivered_rules } from './delivered-rules'
5
-
6
- // The payload machinery two delivery suites share (joshuafolkken/kit#2119). `delivered-rules.test.ts`
7
- // had it first; the filing rules — the scout gate and the per-run cap — need the same temp transcript,
8
- // payload envelope and per-rule stamp cleanup, so a second copy in `delivered-rules-filing.test.ts`
9
- // would be the clone `CLAUDE.md` prohibits. Each suite calls `create_harness()` once, so each gets its
10
- // own work directory and its own tracked transcripts.
11
-
12
- const PRE_TOOL_USE = 'PreToolUse'
13
- const BASH_TOOL = 'Bash'
14
-
15
- interface Harness {
16
- // The temp directory this harness's transcripts live under.
17
- work: string
18
- // Every transcript file written, so a suite can clean the batching stamp keyed on each of them.
19
- written: ReadonlySet<string>
20
- transcript_for: (name: string, text?: string) => string
21
- payload_for: (transcript: string, command: string, tool_name?: string) => string
22
- payload_of: (name: string, command: string, tool_name?: string, history?: string) => string
23
- // Removes every rule's once-per-run stamp for every written transcript, then the work directory.
24
- cleanup: () => void
25
- }
26
-
27
- function payload_for(transcript: string, command: string, tool_name = BASH_TOOL): string {
28
- return JSON.stringify({
29
- hook_event_name: PRE_TOOL_USE,
30
- transcript_path: transcript,
31
- tool_name,
32
- tool_input: { command },
33
- })
34
- }
35
-
36
- function create_harness(prefix = 'rule-guard-'): Harness {
37
- const work = mkdtempSync(path.join(tmpdir(), prefix))
38
- const written = new Set<string>()
39
-
40
- // The trigger reads the call, never the history, so an empty transcript is the honest fixture — it
41
- // proves the decision came from the command rather than from anything behind it.
42
- function transcript_for(name: string, text = ''): string {
43
- const target = path.join(work, `${name}.jsonl`)
44
-
45
- writeFileSync(target, text)
46
- written.add(target)
47
-
48
- return target
49
- }
50
-
51
- // `history` is the transcript behind the call, empty for a case that reads only the command.
52
- function payload_of(name: string, command: string, tool_name = BASH_TOOL, history = ''): string {
53
- return payload_for(transcript_for(name, history), command, tool_name)
54
- }
55
-
56
- function cleanup(): void {
57
- for (const transcript of written) {
58
- for (const rule of delivered_rules.DELIVERED_RULES) {
59
- rmSync(delivered_rules.delivery_path(rule.id, transcript), { force: true })
60
- }
61
- }
62
-
63
- rmSync(work, { recursive: true, force: true })
64
- }
65
-
66
- return { work, written, transcript_for, payload_for, payload_of, cleanup }
67
- }
68
-
69
- const delivered_rules_harness = { create_harness }
70
-
71
- export type { Harness }
72
- export { delivered_rules_harness }
@@ -1,47 +0,0 @@
1
- import { readdirSync } from 'node:fs'
2
- import path from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
-
5
- // `scripts/` is grouped into domain subdirectories (joshuafolkken/kit#2013). A file placed directly
6
- // under `scripts/` is what this guard forbids: flat files are hard to find, coarsen the changed-file
7
- // scope, and inflate an agent's investigation reads. The scripts root is one level above this rule's
8
- // own directory.
9
- const SCRIPTS_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
10
-
11
- // The extensions a top-level file would carry — every executable script is one of these. A stray
12
- // `.md` or `.json` at the root is not what the layout is about, so it is not guarded.
13
- const GUARDED_EXTENSIONS: ReadonlyArray<string> = ['.ts', '.sh']
14
-
15
- // Files permitted directly under `scripts/`. Empty today — every script lives in a domain
16
- // subdirectory — so a new top-level file is a mistake until someone adds it here with a reason.
17
- const TOP_LEVEL_ALLOWLIST: ReadonlySet<string> = new Set<string>()
18
-
19
- function is_guarded_file(name: string): boolean {
20
- return GUARDED_EXTENSIONS.some((extension) => name.endsWith(extension))
21
- }
22
-
23
- // The guarded top-level names that are not allowlisted. Kept pure over a plain listing so its own
24
- // unit test can exercise it without touching the filesystem.
25
- function top_level_offenders(
26
- entries: ReadonlyArray<string>,
27
- allowlist: ReadonlySet<string> = TOP_LEVEL_ALLOWLIST,
28
- ): Array<string> {
29
- return entries.filter((name) => is_guarded_file(name) && !allowlist.has(name))
30
- }
31
-
32
- // The real names sitting directly under `scripts/`, files only — subdirectories are the whole point
33
- // of the layout, so they are never offenders.
34
- function read_top_level_entries(): Array<string> {
35
- return readdirSync(SCRIPTS_ROOT, { withFileTypes: true })
36
- .filter((entry) => entry.isFile())
37
- .map((entry) => entry.name)
38
- }
39
-
40
- const top_level_scripts_guard = {
41
- SCRIPTS_ROOT,
42
- TOP_LEVEL_ALLOWLIST,
43
- top_level_offenders,
44
- read_top_level_entries,
45
- }
46
-
47
- export { top_level_scripts_guard }
@@ -1,58 +0,0 @@
1
- import { PORCELAIN_FLAG, UNTRACKED_FILES_FLAG } from '#scripts/git/constants'
2
- import { git_spawn } from '#scripts/git/git-spawn'
3
- import { josh_command } from '#scripts/josh/josh-run'
4
- import { observation_ledger } from '#scripts/observations/observation-ledger'
5
- import { observation_ledger_home } from '#scripts/observations/observation-ledger-home'
6
-
7
- // The one observation-ledger flush a `backlogrun` makes, at its end (joshuafolkken/kit#2492).
8
- //
9
- // A lane child used to flush after every merge (`run:tail`'s first step): a ledger-only branch, pull
10
- // request and full CI run per issue, minutes each, for a file only the retrospective and the
11
- // measurements read. The lanes now leave their lines appended in the primary checkout — the ledger's
12
- // only home (`observation-ledger-home.ts`) — and `run:carry --end` commits them all in one pull request
13
- // once the record is gone. `--end` is the batch's one end across every session cut, and the second
14
- // `--end` finds no record and flushes nothing, so the flush is once per invocation.
15
- //
16
- // **It runs after the retrospective, not before**, and loses nothing by it: the retrospective reads the
17
- // ledger file on disk (`retrospective-cli.ts` → `read_ledger`), not main, and the lines it stacks
18
- // itself land in the same pull request. **A line a cut or a crash left unflushed is not lost either**:
19
- // it is still a pending append in the primary checkout, and the next flush — this one, a later run's,
20
- // or a single run's `followup` — sees it from `git status`, whichever run wrote it.
21
- //
22
- // **A failed flush is reported, never allowed to keep the record standing.** The lines stay on disk for
23
- // the next flush, while a record that outlived its run would make the next `--begin` read `busy` — so
24
- // `--end` clears the record before it flushes. A flush killed before its commit leaves only the lines
25
- // in the working tree; one killed after it leaves them on its pull request's branch, which GitHub's
26
- // auto-merge lands once the checks pass (joshuafolkken/kit#2497).
27
-
28
- const FLUSH_ARGV: ReadonlyArray<string> = ['observations:flush']
29
- const SUCCESS_EXIT_CODE = 0
30
- const FAILURE_NOTE =
31
- 'The observation ledger flush failed; its lines stay in the primary checkout for the next flush (`pnpm josh observations:flush`).'
32
-
33
- async function has_pending_append(): Promise<boolean> {
34
- const root = observation_ledger_home.ledger_root()
35
- const status = await git_spawn.read(['-C', root, 'status', PORCELAIN_FLAG, UNTRACKED_FILES_FLAG])
36
-
37
- return observation_ledger.has_pending_append(status)
38
- }
39
-
40
- // Standard output is `run:carry`'s one-token contract, so every word of the flush goes to standard
41
- // error — the flush's own explanation is forwarded there, and its report is copied after it.
42
- async function flush_ledger(): Promise<void> {
43
- try {
44
- if (!(await has_pending_append())) return
45
-
46
- const result = await josh_command.josh_run(FLUSH_ARGV, true)
47
-
48
- if (result.out.length > 0) console.error(result.out)
49
-
50
- if (result.code !== SUCCESS_EXIT_CODE) console.error(FAILURE_NOTE)
51
- } catch {
52
- console.error(FAILURE_NOTE)
53
- }
54
- }
55
-
56
- const run_carry_flush = { FAILURE_NOTE, FLUSH_ARGV, flush_ledger }
57
-
58
- export { run_carry_flush }
@@ -1,57 +0,0 @@
1
- ## child-completion
2
- a launched child runs through to its merge
3
- start verdict implement
4
- planned verdict implement
5
- child launched verdict wait
6
- child merged command pnpm josh run:merge 2250
7
-
8
- ## park
9
- a child is parked and the batch moves on
10
- start verdict implement
11
- child parked command pnpm josh backlog:next
12
-
13
- ## cut-and-resume
14
- the run is cut before its gate, then resumes to the merge
15
- start verdict implement
16
- cut taken command pnpm josh run:cut --resume 2250
17
- resumed, pr open command pnpm josh followup
18
- second review command pnpm josh review:round2
19
- merged command pnpm josh run:merge 2250
20
-
21
- ## lanes-full
22
- every lane holds a child, so the parent waits
23
- start verdict implement
24
- lanes full verdict wait
25
-
26
- ## consecutive-failure-stop
27
- the failure guard stops the run
28
- start verdict implement
29
- outage command pnpm josh run:merge 2250
30
- guard stops command pnpm josh retrospective
31
-
32
- ## drain-retrospective
33
- the backlog drains, so the retrospective runs before the idle watch
34
- start verdict implement
35
- backlog drained command pnpm josh retrospective
36
- retrospective done verdict wait
37
-
38
- ## expired-budget
39
- the whole-run budget is spent — a person decides
40
- start decide decide: whole-run budget spent — resume with `pnpm josh run:carry --resume` | discard with `pnpm josh run:carry --end`
41
-
42
- ## unreadable-carry
43
- the carry record cannot be read
44
- start verdict unknown
45
-
46
- ## closed-already-done
47
- the issue is already closed, whatever the events say
48
- start verdict already-done
49
- after a merge verdict already-done
50
-
51
- ## needs-human-review
52
- the issue stops before its commit for a person
53
- start verdict human-review
54
-
55
- ## update-deps-first
56
- a required dependency update comes before implementing
57
- start verdict update-deps
@@ -1,108 +0,0 @@
1
- import { backlog_stalled } from '#scripts/backlog/backlog-stalled'
2
- import { josh_command } from '#scripts/josh/josh-run'
3
- import { lane_capacity } from '#scripts/lane/lane-capacity'
4
- import { lane_registry } from '#scripts/lane/lane-registry'
5
- import { run_carry, type CarryRead } from './run-carry'
6
- import { run_invocation } from './run-invocation'
7
-
8
- // Whether a session the supervisor is about to wake would find anything to do (joshuafolkken/kit#2417).
9
- //
10
- // **This is what stops the whiff at its source.** The wake decision read the carry record alone, so a
11
- // session was launched whether or not there was work, and a woken session that found the backlog empty
12
- // or every lane taken ended without touching anything — a full agent session, preamble and all, bought
13
- // to learn one boolean. Asked here, in the supervisor's own process, the same boolean costs a
14
- // `git worktree list` and at most one `backlog:next`, and no session at all.
15
- //
16
- // **Cheap first, as the stall detector reads it.** The named issues left on the carry record need no
17
- // I/O; the free-lane count is local; `backlog:next` is the one network read, and it is made only where
18
- // a lane is free for its answer to go into. Both counts are read with the stall detector's primitives
19
- // (`lane_capacity`, `lane_registry`, `count_ready_tokens`), so "a free lane and a ready issue" means one
20
- // thing in both places.
21
- //
22
- // **`undefined` is "cannot tell", and the caller wakes on it.** A failed read is not an empty pool: read
23
- // as one, an outage would defer every launch until the idle ceiling, stalling a run that had work. That
24
- // is why neither of the stall detector's readers is reused whole — it reads an unreadable lane limit
25
- // and a failed `backlog:next` as zero, the safe direction for a stall report and the wrong one here —
26
- // and why a read that throws (a `git worktree list` caught mid-prune) is caught rather than ending the
27
- // supervisor.
28
-
29
- const NO_FREE = 0
30
- const NO_READY = 0
31
- const SUCCESS_EXIT_CODE = 0
32
- const TRIAGE_WORK = 1
33
-
34
- interface WorkPorts {
35
- // The free-lane count, or `undefined` where the lane limit could not be read.
36
- free_lane_count: () => Promise<number | undefined>
37
- // The runnable count, or `undefined` where `backlog:next` could not answer.
38
- ready_count: () => Promise<number | undefined>
39
- }
40
-
41
- async function real_ready_count(): Promise<number | undefined> {
42
- const result = await josh_command.josh_run(['backlog:next'], true)
43
-
44
- if (result.code !== SUCCESS_EXIT_CODE) return undefined
45
- // A `triage` answer offers no number, but the untriaged issues are a session's to judge
46
- // (joshuafolkken/kit#2779) — counted as one piece of work, or the whole pool would idle with nobody judging it.
47
- if (backlog_stalled.needs_triage(result.out)) return TRIAGE_WORK
48
-
49
- return backlog_stalled.count_ready_tokens(result.out)
50
- }
51
-
52
- async function real_free_lane_count(): Promise<number | undefined> {
53
- const limit = lane_capacity.lane_limit()
54
-
55
- if (limit.kind !== 'limit') return undefined
56
-
57
- const lanes = await lane_registry.list_lanes()
58
- const live = lanes.filter((lane) => !lane.is_stranded).length
59
-
60
- return lane_capacity.free_lanes(limit.limit, live)
61
- }
62
-
63
- const DEFAULT_WORK_PORTS: WorkPorts = {
64
- free_lane_count: real_free_lane_count,
65
- ready_count: real_ready_count,
66
- }
67
-
68
- // **Work the record answers without a read.** Named issues still to run are work by definition, and a
69
- // `--only` run whose named list is done is work of a different kind: the session is needed to finish
70
- // the run, and there is no pool it could be waiting on — deferring it would only delay the report.
71
- function is_owed_by_record(read: CarryRead): boolean {
72
- if (read.kind !== 'carried') return true
73
-
74
- const remaining = run_carry.remaining_of(read.carry)
75
-
76
- if (remaining !== undefined && remaining.length > NO_READY) return true
77
-
78
- return run_invocation.has_only(read.carry.invocation)
79
- }
80
-
81
- async function read_pool(ports: WorkPorts): Promise<boolean | undefined> {
82
- const free = await ports.free_lane_count()
83
-
84
- if (free === undefined) return undefined
85
- if (free <= NO_FREE) return false
86
-
87
- const ready = await ports.ready_count()
88
-
89
- return ready === undefined ? undefined : ready > NO_READY
90
- }
91
-
92
- async function has_work(
93
- read: CarryRead,
94
- ports: WorkPorts = DEFAULT_WORK_PORTS,
95
- ): Promise<boolean | undefined> {
96
- if (is_owed_by_record(read)) return true
97
-
98
- try {
99
- return await read_pool(ports)
100
- } catch {
101
- return undefined
102
- }
103
- }
104
-
105
- const run_wake_work = { DEFAULT_WORK_PORTS, has_work }
106
-
107
- export type { WorkPorts }
108
- export { run_wake_work }