@joshuafolkken/kit 1.1010.0 → 1.1050.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +1 -5
- package/.claude/skills/dependency-update/SKILL.md +6 -3
- package/.claude/skills/epic-commands/SKILL.md +46 -17
- package/.claude/skills/verify-ui/SKILL.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +10 -25
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
- package/.claude/skills/workflow-commands/backlogrun-progress.md +30 -36
- package/.claude/skills/workflow-commands/backlogrun-steps.md +16 -16
- package/.claude/skills/workflow-commands/backlogrun.md +3 -12
- package/.claude/skills/workflow-commands/chain-rule.md +2 -2
- package/.claude/skills/workflow-commands/followup-reference.md +2 -2
- package/.claude/skills/workflow-commands/fullrun-steps.md +4 -3
- package/.claude/skills/workflow-commands/fullrun.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +2 -1
- package/.claude/skills/workflow-commands/into-target.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +1 -1
- package/.claude/skills/workflow-commands/observation-filing.md +80 -126
- package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -36
- package/.claude/skills/workflow-commands/prerequisite.md +1 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +2 -2
- package/.codex/hooks.json +1 -5
- package/.github/workflows/auto-tag.yml +21 -0
- package/.github/workflows/publish.yml +9 -10
- package/.github/workflows/sonar-qube.yml +1 -1
- package/AGENTS.md +4 -25
- package/CLAUDE.md +24 -38
- package/GEMINI.md +4 -24
- package/README.md +22 -56
- package/dist/CLAUDE.md +24 -38
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-6PSTVOLH.js → chunk-5BK3K7V4.js} +114 -90
- package/dist/hooks/{chunk-PAXMLGSS.js → chunk-7VPDUBRN.js} +93 -175
- package/dist/hooks/{chunk-KPZN42RT.js → chunk-DMQN45FN.js} +3 -3
- package/dist/hooks/{chunk-WMIR5SNX.js → chunk-EJDX2TOB.js} +13 -3
- package/dist/hooks/{chunk-A2H5I2VD.js → chunk-GQ7KP45M.js} +2 -2
- package/dist/hooks/{chunk-4GDR2YTV.js → chunk-HJIISEAC.js} +4 -4
- package/dist/hooks/{chunk-AWTFKKFW.js → chunk-NAS4C7GP.js} +6 -33
- package/dist/hooks/{chunk-56LGMJJV.js → chunk-NJQOBE6L.js} +2 -2
- package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-RLDX5SOC.js} +1222 -941
- package/dist/hooks/{chunk-GBJUERYK.js → chunk-WQ4OSBSS.js} +10 -3
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +15 -34
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +23 -16
- package/dist/hooks/stop-guard.js +21 -200
- package/dist/josh.js +42 -125
- package/dist/version/index.js +466 -42
- package/eslint/base.js +2 -11
- package/package.json +36 -10
- package/prompts/coding-standards.md +15 -290
- package/prompts/collaboration-workflow/cross-repo-epic.md +2 -51
- package/prompts/collaboration-workflow/epic-audit.md +2 -53
- package/prompts/collaboration-workflow/epic-bundle.md +2 -106
- package/prompts/collaboration-workflow/file-edits.md +6 -53
- package/prompts/collaboration-workflow/issue-template.md +4 -4
- package/prompts/collaboration-workflow/operating-rules.md +18 -75
- package/prompts/collaboration-workflow/principles.md +74 -0
- package/prompts/collaboration-workflow/residency.md +55 -15
- package/prompts/collaboration-workflow/rule-delivery.md +5 -46
- package/prompts/collaboration-workflow/shell-body.md +7 -9
- package/prompts/collaboration-workflow/turn-batching.md +21 -37
- package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
- package/prompts/collaboration-workflow/wip-cap.md +15 -18
- package/prompts/collaboration-workflow.md +22 -28
- package/prompts/sonar-hotspot-handling.md +1 -1
- package/scripts/adopt/adopt-logic.ts +2 -2
- package/scripts/adopt/adopt-toolkits.ts +3 -1
- package/scripts/adopt/adopt.ts +2 -2
- package/scripts/agent/agent-event.ts +2 -9
- package/scripts/agent/agent-exit-record.ts +2 -9
- package/scripts/auto-ok/auto-ok-cli.ts +9 -9
- package/scripts/backlog/backlog-budget-cli.ts +2 -6
- package/scripts/backlog/backlog-drive-cli.ts +20 -10
- package/scripts/backlog/backlog-drive.ts +50 -16
- package/scripts/backlog/backlog-next.ts +24 -8
- package/scripts/backlog/backlog-offer-cli.ts +6 -5
- package/scripts/backlog/backlog-plan-cli.ts +5 -1
- package/scripts/backlog/backlog-plan.ts +1 -1
- package/scripts/backlog/backlog-pool.ts +31 -11
- package/scripts/backlog/backlog-rank.ts +106 -0
- package/scripts/backlog/backlog-waves.ts +42 -29
- package/scripts/build/build-bin.ts +2 -1
- package/scripts/build/build-config-merge.ts +2 -1
- package/scripts/build/build-hooks.ts +31 -6
- package/scripts/build/build-managed-marker.ts +2 -1
- package/scripts/build/build-self-sync-guard.ts +2 -1
- package/scripts/build/build-version.ts +2 -1
- package/scripts/cases/cases-logic.ts +2 -6
- package/scripts/ci/pr-classification.ts +13 -11
- package/scripts/cost-runtime/cost-transcript.ts +4 -11
- package/scripts/delegation/delegation-policy.ts +11 -0
- package/scripts/doctor/doctor-consumer.ts +5 -12
- package/scripts/doctor/doctor-ports.ts +2 -2
- package/scripts/document/document-byte-budget.ts +13 -21
- package/scripts/document/document-section.ts +2 -6
- package/scripts/document/entry-read-budget.ts +6 -5
- package/scripts/{git/git-epic-add-body.ts → epic/epic-add-body.ts} +23 -23
- package/scripts/{git/git-epic-add-plan.ts → epic/epic-add-plan.ts} +23 -26
- package/scripts/{git/git-epic-add.ts → epic/epic-add.ts} +17 -17
- package/scripts/epic/epic-audit-cli.ts +4 -4
- package/scripts/epic/epic-audit-orphans.ts +2 -2
- package/scripts/epic/epic-audit-rationale.ts +1 -1
- package/scripts/epic/epic-audit.ts +1 -1
- package/scripts/{git/git-epic-body.ts → epic/epic-body.ts} +6 -6
- package/scripts/epic/epic-bundle-cli.ts +1 -1
- package/scripts/epic/epic-bundle.ts +2 -2
- package/scripts/epic/epic-candidate-confirm.ts +2 -1
- package/scripts/{git/git-epic-chains.ts → epic/epic-chains.ts} +4 -4
- package/{scripts-ai → scripts/epic}/epic-check.ts +5 -4
- package/{scripts-ai → scripts/epic}/epic-cli.ts +4 -4
- package/scripts/{git/git-epic-close-comment.ts → epic/epic-close-comment.ts} +9 -9
- package/scripts/{git/git-epic-close.ts → epic/epic-close.ts} +23 -19
- package/scripts/epic/epic-cross-repo.ts +21 -38
- package/scripts/{git/git-epic-decision.ts → epic/epic-decision.ts} +12 -14
- package/scripts/epic/epic-fetch.ts +3 -3
- package/scripts/epic/epic-graph.ts +3 -3
- package/scripts/epic/epic-index.ts +2 -2
- package/scripts/epic/epic-issue.ts +1 -1
- package/scripts/epic/epic-lane-offer.ts +11 -22
- package/scripts/epic/epic-next.ts +3 -3
- package/scripts/{git/git-epic-parse.ts → epic/epic-parse.ts} +3 -3
- package/scripts/{git/git-epic-promote.ts → epic/epic-promote.ts} +10 -10
- package/scripts/epic/epic-rank.ts +37 -0
- package/scripts/{git/git-epic-read.ts → epic/epic-read.ts} +9 -9
- package/scripts/{git/git-epic-reconcile.ts → epic/epic-reconcile.ts} +18 -20
- package/scripts/{git/git-epic-reference.ts → epic/epic-reference.ts} +4 -23
- package/scripts/epic/epic-relation-recheck.ts +4 -3
- package/scripts/{git/git-epic-relations.ts → epic/epic-relations.ts} +5 -5
- package/scripts/{git/git-epic-remove-plan.ts → epic/epic-remove-plan.ts} +17 -19
- package/scripts/{git/git-epic-remove.ts → epic/epic-remove.ts} +13 -13
- package/scripts/{git/git-epic-run.ts → epic/epic-run.ts} +27 -27
- package/scripts/{git/git-epic-sections.ts → epic/epic-sections.ts} +6 -6
- package/scripts/{git/git-epic-shape.ts → epic/epic-shape.ts} +7 -7
- package/scripts/epic/epic-solo.ts +29 -28
- package/scripts/{git/git-epic-validate.ts → epic/epic-validate.ts} +13 -13
- package/{scripts-ai → scripts/epic}/epic.ts +17 -16
- package/scripts/gh/fix-gh-packages.ts +2 -1
- package/scripts/gh/gh-spawn.ts +12 -10
- package/scripts/git/constants.ts +1 -1
- package/scripts/git/git-branch.ts +10 -1
- package/scripts/git/git-command.ts +2 -2
- package/scripts/git/git-error.ts +2 -5
- package/scripts/git/git-execa-error.ts +6 -1
- package/{scripts-ai → scripts/git}/git-followup-finish.ts +7 -7
- package/scripts/git/git-followup-flush.ts +74 -48
- package/scripts/git/git-followup-issue-close.ts +2 -2
- package/scripts/git/git-followup-label.ts +1 -1
- package/scripts/git/git-followup-pending.ts +1 -1
- package/{scripts-ai → scripts/git}/git-followup-workflow.ts +23 -11
- package/scripts/git/git-gh-exec.ts +85 -26
- package/scripts/git/git-gh-issue-read.ts +2 -2
- package/scripts/git/git-gh-issue-rest.ts +2 -2
- package/scripts/git/git-gh-issue-write.ts +6 -6
- package/scripts/git/git-gh-pr-read.ts +0 -21
- package/scripts/git/git-gh-pr.ts +2 -23
- package/scripts/git/git-next-issues.ts +34 -5
- package/scripts/git/git-pr-followup-wrapup.ts +2 -6
- package/scripts/git/git-preflight.ts +25 -10
- package/scripts/git/git-push-transport.ts +12 -69
- package/scripts/git/git-spawn.ts +79 -3
- package/scripts/git/git-ssh-keepalive.ts +74 -0
- package/scripts/git/git-staging.ts +21 -9
- package/{scripts-ai → scripts/git}/git-workflow.ts +11 -11
- package/scripts/git/github-issue-url.ts +1 -1
- package/scripts/git/gone-branch.ts +3 -2
- package/scripts/git/issue-labels.ts +13 -1
- package/scripts/git/issue-rank.ts +89 -0
- package/scripts/git/issue-reference.ts +19 -0
- package/scripts/git/main-merge.ts +2 -1
- package/scripts/git/main-sync.ts +3 -2
- package/scripts/git/schemas.ts +1 -1
- package/scripts/git/stash-pop-args.ts +7 -13
- package/scripts/git/stash-pop-cli.ts +2 -1
- package/{scripts-ai → scripts/git}/telegram-test-logic.ts +2 -2
- package/{scripts-ai → scripts/git}/telegram-test.ts +7 -7
- package/scripts/hooks/format-edited-cli.ts +15 -0
- package/scripts/hooks/format-edited-file.ts +2 -9
- package/scripts/hooks/format-rewrite-notice.ts +2 -8
- package/scripts/hooks/pretool-guard-cli.ts +21 -0
- package/scripts/hooks/pretool-guard.ts +14 -15
- package/scripts/init/init-ai-copy.ts +2 -5
- package/scripts/init/init.ts +8 -4
- package/scripts/init/start-exec.ts +7 -1
- package/scripts/init/start-plan.ts +18 -5
- package/scripts/init/start-setup-pr.ts +46 -19
- package/scripts/init/start-steps.ts +43 -10
- package/scripts/init/start.ts +42 -11
- package/scripts/issue/issue-cite.ts +19 -1
- package/scripts/issue/issue-comment-cli.ts +2 -1
- package/scripts/issue/issue-file-cli.ts +2 -1
- package/scripts/issue/issue-fold-cli.ts +9 -16
- package/scripts/issue/issue-scout-cli.ts +8 -13
- package/scripts/issue/measure-rerun-cli.ts +7 -6
- package/scripts/josh/josh-command-map.ts +0 -102
- package/scripts/josh/josh-commands-ai.ts +2 -2
- package/scripts/josh/josh-commands-backlog.ts +1 -1
- package/scripts/josh/josh-commands-development.ts +3 -3
- package/scripts/josh/josh-commands-project.ts +1 -1
- package/scripts/josh/josh-commands-workflow.ts +4 -4
- package/scripts/lane/lane-child-marker.ts +16 -2
- package/scripts/lane/lane-cli.ts +34 -15
- package/scripts/lane/lane-open.ts +3 -2
- package/scripts/lane/lane-registry.ts +3 -6
- package/scripts/lane/lane-report.ts +10 -4
- package/scripts/lib/cli-flags.ts +20 -2
- package/scripts/lib/error-message.ts +9 -0
- package/scripts/lib/read-file.ts +26 -2
- package/scripts/observations/observation-ledger-home.ts +98 -26
- package/scripts/observations/observation-ledger-migrate.ts +26 -21
- package/scripts/observations/observation-ledger-prepare.ts +10 -20
- package/scripts/observations/observation-ledger.ts +58 -25
- package/scripts/observations/observations-flush-cli.ts +6 -23
- package/scripts/observations/observations-flush-landing.ts +2 -1
- package/scripts/observations/observations-flush.ts +29 -20
- package/scripts/package/package-scout-cli.ts +8 -16
- package/scripts/ports/port-command.ts +2 -1
- package/scripts/propagate/propagate-plan.ts +185 -0
- package/scripts/propagate/propagate-publish.ts +6 -7
- package/scripts/propagate/propagate-run.ts +5 -1
- package/scripts/propagate/propagate-steps.ts +18 -2
- package/scripts/propagate/propagate-targets.ts +13 -2
- package/scripts/propagate/propagate.ts +63 -44
- package/scripts/refactor/import-graph.ts +2 -6
- package/scripts/registry-migration/migrate-logic.ts +1 -0
- package/scripts/registry-migration/migrate.ts +3 -2
- package/scripts/registry-migration/user-npmrc.ts +2 -2
- package/scripts/release/github-release.ts +1 -1
- package/scripts/release/release-cli.ts +2 -1
- package/scripts/repo/repo-setting.ts +10 -8
- package/scripts/review/review-attest.ts +44 -0
- package/scripts/review/review-brief-cli.ts +49 -2
- package/scripts/review/review-brief.ts +46 -1
- package/scripts/review/review-finding-ledger.ts +3 -3
- package/scripts/review/review-findings-cli.ts +4 -11
- package/scripts/review/review-record-cli.ts +14 -11
- package/scripts/review/review-record.ts +6 -16
- package/scripts/review/review-stamps.ts +7 -0
- package/scripts/rules/decision-oracle.ts +2 -2
- package/scripts/rules/implementation-cut.ts +1 -1
- package/scripts/rules/issue-fold.ts +1 -2
- package/scripts/rules/lane-carry-conflict.ts +1 -2
- package/scripts/rules/lane-park.ts +1 -2
- package/scripts/rules/pre-gate-cut.ts +2 -2
- package/scripts/rules/rule-body-guard.ts +2 -2
- package/scripts/rules/rule-value-cache.ts +99 -0
- package/scripts/rules/rule-value-cli.ts +10 -9
- package/scripts/rules/rule-value.ts +1 -1
- package/scripts/run/run-carry-args.ts +2 -6
- package/scripts/run/run-carry-cli.ts +5 -7
- package/scripts/run/run-cut-args.ts +7 -13
- package/scripts/run/run-ending-cli.ts +4 -7
- package/scripts/run/run-event-stream.ts +2 -5
- package/scripts/run/run-headless.ts +22 -6
- package/scripts/run/run-liveness-cli.ts +4 -7
- package/scripts/run/run-parent-cut-hook.ts +144 -0
- package/scripts/run/run-progress-args.ts +2 -6
- package/scripts/run/run-progress-cli.ts +2 -1
- package/scripts/run/run-progress-read.ts +2 -1
- package/scripts/run/run-progress.ts +21 -8
- package/scripts/run/run-ship-cli.ts +6 -74
- package/scripts/run/run-ship-preflight.ts +87 -0
- package/scripts/run/run-ship-review-steps.ts +5 -26
- package/scripts/run/run-ship-review.ts +9 -0
- package/scripts/run/run-ship-scoped.ts +44 -0
- package/scripts/run/run-ship-stage.ts +5 -1
- package/scripts/run/run-ship-steps.ts +105 -0
- package/scripts/run/run-ship.ts +2 -0
- package/scripts/run/run-tail-cli.ts +3 -3
- package/scripts/run/run-tidy-stashes.ts +7 -14
- package/scripts/run/run-wake-cli.ts +4 -8
- package/scripts/run/run-wake-describe.ts +19 -1
- package/scripts/run/run-wake-driver.ts +15 -2
- package/scripts/run/run-wake-session.ts +14 -4
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/safe-chain/project-config.ts +3 -6
- package/scripts/security/security-audit-provision.ts +6 -7
- package/scripts/sonar/sonar-hotspots-cli.ts +2 -1
- package/scripts/sync/directory-copy-guard.ts +2 -1
- package/scripts/sync/sync-ai-files.ts +3 -3
- package/scripts/sync/template-source-logic.ts +3 -4
- package/scripts/test/e2e-retry-check.ts +3 -6
- package/scripts/time-runtime/time-bundle-call.ts +2 -2
- package/scripts/ui/ui-routes-cli.ts +3 -6
- package/scripts/version/bump-version.ts +2 -1
- package/scripts/version/npm-registry.ts +97 -0
- package/scripts/version/version-commands.ts +5 -4
- package/scripts/version/version-remote.ts +38 -33
- package/scripts/version/version-targets.ts +3 -3
- package/.claude/skills/workflow-commands/rule-residency.md +0 -267
- package/evals/scenarios/consult-not-execute.json +0 -20
- package/evals/scenarios/no-implicit-workflow.json +0 -26
- package/evals/scenarios/overrides-both-locations.json +0 -29
- package/evals/scenarios/upstream-interrupt.json +0 -23
- package/evals/scenarios/workflow-skill-loaded.json +0 -16
- package/prompts/agent-lint-protocol.json +0 -79
- package/prompts/agent-rules.md +0 -63
- package/prompts/collaboration-workflow/consultation-vs-execution.md +0 -8
- package/prompts/collaboration-workflow/distributed-docs.md +0 -7
- package/prompts/collaboration-workflow/durable-rules.md +0 -24
- package/prompts/collaboration-workflow/latest-first.md +0 -14
- package/prompts/collaboration-workflow/no-clones.md +0 -11
- package/prompts/collaboration-workflow/simplicity-first.md +0 -9
- package/prompts/collaboration-workflow/single-source-rules.md +0 -13
- package/scripts/build/shim-shell.ts +0 -25
- package/scripts/claude/skill-meta.ts +0 -37
- package/scripts/document/document-scan.ts +0 -108
- package/scripts/document/generate-catalog.ts +0 -69
- package/scripts/document/retired-phrases.ts +0 -143
- package/scripts/document/review-agent-type.ts +0 -46
- package/scripts/document/single-source.ts +0 -73
- package/scripts/git/git-fixture-workspace.ts +0 -88
- package/scripts/hooks/format-edited-file-fixtures.ts +0 -109
- package/scripts/hooks/prompt-hooks.ts +0 -38
- package/scripts/init/hook-command-bootstrap.ts +0 -83
- package/scripts/issue/issue-logic.ts +0 -50
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +0 -1227
- package/scripts/josh/josh-script-reader.ts +0 -11
- package/scripts/package/production-dependency-graph.ts +0 -68
- package/scripts/retrospective/retrospective-cli.ts +0 -97
- package/scripts/retrospective/retrospective.ts +0 -127
- package/scripts/rules/delivered-rules-harness.ts +0 -72
- package/scripts/rules/top-level-scripts-guard.ts +0 -47
- package/scripts/run/run-carry-flush.ts +0 -58
- package/scripts/run/run-transcript.golden.txt +0 -57
- package/scripts/run/run-wake-work.ts +0 -108
- package/scripts/sync/dependabot-pin-logic.ts +0 -135
- package/scripts/test/classify-isolation.ts +0 -149
- package/scripts/test/http-fault-injection.ts +0 -79
- package/scripts/test/josh-harness-environment.ts +0 -260
- package/scripts/test/josh-harness.ts +0 -96
- package/scripts/test/pilot-files.ts +0 -452
- package/scripts/test/pilot-measure.ts +0 -78
- package/scripts/test/test-network-guard.ts +0 -453
- package/scripts/test/test-repository-guard.ts +0 -317
- package/scripts/test/test-state-guard.ts +0 -85
- package/scripts/test/test-stdout-guard.ts +0 -54
- package/scripts/test/test-telegram-guard.ts +0 -90
- package/scripts/test/unit-guard-environment.ts +0 -12
- package/scripts/test/unit-projects.ts +0 -139
- package/scripts/test/vitest-include-globs.ts +0 -12
- package/scripts/time-runtime/time-batch-guard-fixtures.ts +0 -66
- package/scripts-ai/completion-gate.ts +0 -13
- package/scripts-ai/environment-loader.ts +0 -9
- /package/{scripts-ai → scripts/epic}/epic-cli-argv.ts +0 -0
- /package/{scripts-ai → scripts/epic}/epic-cli-remove.ts +0 -0
- /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 }
|