@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.
- 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/workflow-commands/SKILL.md +8 -23
- package/.claude/skills/workflow-commands/backlogrun-child.md +4 -2
- package/.claude/skills/workflow-commands/backlogrun.md +3 -12
- 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/observation-filing.md +42 -43
- package/.claude/skills/workflow-commands/prerequisite.md +1 -1
- package/.codex/hooks.json +1 -5
- 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 -58
- package/dist/CLAUDE.md +24 -38
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-WMIR5SNX.js → chunk-D67KVGGY.js} +3 -3
- package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-EIFCAJRB.js} +70 -27
- package/dist/hooks/{chunk-KPZN42RT.js → chunk-GJT4BNVE.js} +3 -3
- package/dist/hooks/{chunk-PAXMLGSS.js → chunk-GLBDBJOK.js} +44 -24
- package/dist/hooks/{chunk-GBJUERYK.js → chunk-HOYJU5RM.js} +1 -1
- package/dist/hooks/{chunk-6PSTVOLH.js → chunk-HU66PWWL.js} +33 -57
- package/dist/hooks/{chunk-A2H5I2VD.js → chunk-KG6PZD72.js} +2 -2
- package/dist/hooks/{chunk-AWTFKKFW.js → chunk-XPLP27WA.js} +3 -8
- package/dist/hooks/{chunk-4GDR2YTV.js → chunk-Y27M6A7X.js} +4 -4
- package/dist/hooks/{chunk-56LGMJJV.js → chunk-ZXNE6NY3.js} +2 -2
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +9 -9
- package/dist/hooks/stop-guard.js +11 -15
- package/dist/josh.js +38 -19
- package/dist/version/index.js +93 -41
- package/eslint/base.js +2 -11
- package/package.json +32 -6
- 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/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 +54 -14
- package/prompts/collaboration-workflow/rule-delivery.md +1 -11
- package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
- package/prompts/collaboration-workflow.md +22 -28
- package/scripts/adopt/adopt-logic.ts +2 -2
- package/scripts/adopt/adopt-toolkits.ts +1 -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/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-offer-cli.ts +6 -5
- package/scripts/backlog/backlog-plan.ts +1 -1
- package/scripts/build/build-bin.ts +2 -1
- package/scripts/build/build-config-merge.ts +2 -1
- package/scripts/build/build-hooks.ts +24 -4
- 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/doctor/doctor-consumer.ts +5 -12
- package/scripts/doctor/doctor-ports.ts +2 -2
- package/scripts/document/document-byte-budget.ts +8 -16
- package/scripts/document/document-section.ts +2 -6
- package/scripts/document/entry-read-budget.ts +1 -1
- 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-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/{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/{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 +1 -1
- package/scripts/git/git-error.ts +2 -5
- 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-pr-followup-wrapup.ts +2 -6
- package/scripts/git/git-preflight.ts +3 -6
- 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/issue-labels.ts +1 -1
- 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-rewrite-notice.ts +2 -8
- package/scripts/init/init-ai-copy.ts +23 -14
- package/scripts/init/init.ts +8 -4
- package/scripts/init/start-steps.ts +2 -1
- package/scripts/init/start.ts +2 -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-commands-ai.ts +2 -2
- package/scripts/josh/josh-commands-backlog.ts +1 -1
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-workflow.ts +4 -4
- package/scripts/lane/lane-cli.ts +2 -1
- package/scripts/lane/lane-open.ts +3 -2
- package/scripts/lane/lane-registry.ts +3 -6
- 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-publish.ts +6 -7
- package/scripts/propagate/propagate-steps.ts +2 -1
- package/scripts/propagate/propagate.ts +3 -2
- package/scripts/refactor/import-graph.ts +2 -6
- package/scripts/registry-migration/migrate.ts +3 -2
- package/scripts/registry-migration/user-npmrc.ts +2 -2
- package/scripts/release/release-cli.ts +2 -1
- package/scripts/repo/repo-setting.ts +10 -8
- 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/rules/decision-oracle.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-liveness-cli.ts +4 -7
- package/scripts/run/run-progress-args.ts +2 -6
- package/scripts/run/run-progress-cli.ts +2 -1
- 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 +2 -6
- package/scripts/run/run-wake-driver.ts +15 -2
- package/scripts/run/run-wake-session.ts +14 -4
- 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/ui/ui-routes-cli.ts +3 -6
- package/scripts/version/bump-version.ts +2 -1
- package/scripts/version/version-remote.ts +18 -27
- 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
|
@@ -32,7 +32,7 @@ function is_toolkit_package(package_name: string): boolean {
|
|
|
32
32
|
|
|
33
33
|
// kit is the base tier and every other toolkit distributes files *derived* from kit's, so the two
|
|
34
34
|
// can manage the same path and whichever `sync` ran last decides its contents
|
|
35
|
-
// (`docs/sync.md` → "
|
|
35
|
+
// (`docs/maintainers/sync-rationale.md` → "The managed-workflow stamp"). Ordering is therefore base-first: kit syncs, then the
|
|
36
36
|
// toolkits that overlay it. Sorting by name would put kit last and have it overwrite the overlay
|
|
37
37
|
// with the original every single run.
|
|
38
38
|
function toolkit_rank(package_name: string): number {
|
package/scripts/adopt/adopt.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
4
|
-
import {
|
|
4
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
5
|
import { propagate_run, type TargetResult } from '#scripts/propagate/propagate-run'
|
|
6
6
|
import { propagate_steps, type Release, type ReleasePlan } from '#scripts/propagate/propagate-steps'
|
|
7
7
|
import type { PropagateTarget } from '#scripts/propagate/propagate-targets'
|
|
@@ -40,7 +40,7 @@ interface RunOptions {
|
|
|
40
40
|
|
|
41
41
|
function parse_options(argv: ReadonlyArray<string>): RunOptions {
|
|
42
42
|
const options: RunOptions = { is_dry_run: argv.includes(DRY_RUN_FLAG) }
|
|
43
|
-
const usage = refuse_unknown_flags(argv, KNOWN_FLAGS, 'adopt')
|
|
43
|
+
const usage = cli_flags.refuse_unknown_flags(argv, KNOWN_FLAGS, 'adopt')
|
|
44
44
|
|
|
45
45
|
return usage === undefined ? options : { ...options, usage }
|
|
46
46
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
1
2
|
import { z } from 'zod'
|
|
2
3
|
import type { AgentProvider } from './agent-role-profile'
|
|
3
4
|
import { claude_result_event } from './claude-result-event'
|
|
@@ -53,14 +54,6 @@ const EMPTY_STATE: AgentEventState = {
|
|
|
53
54
|
const LAUNCH_HEADER_PREFIX = '=== '
|
|
54
55
|
const LAUNCH_HEADER_MARKER = ' · started by process '
|
|
55
56
|
|
|
56
|
-
function parse_line(line: string): unknown {
|
|
57
|
-
try {
|
|
58
|
-
return JSON.parse(line)
|
|
59
|
-
} catch {
|
|
60
|
-
return undefined
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
57
|
function usable_text(value: unknown): string | undefined {
|
|
65
58
|
return typeof value === 'string' && value.trim() !== '' ? value : undefined
|
|
66
59
|
}
|
|
@@ -149,7 +142,7 @@ function next_state(line: string, state: AgentEventState): AgentEventState {
|
|
|
149
142
|
const is_header = line.startsWith(LAUNCH_HEADER_PREFIX) && line.includes(LAUNCH_HEADER_MARKER)
|
|
150
143
|
if (is_header) return EMPTY_STATE
|
|
151
144
|
|
|
152
|
-
return normalized(
|
|
145
|
+
return normalized(json_value.parse_or_undefined(line), state)
|
|
153
146
|
}
|
|
154
147
|
|
|
155
148
|
function read(transcript: string): AgentEventState {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
1
2
|
import { claude_result_event, type ClaudeResultEvent } from './claude-result-event'
|
|
2
3
|
|
|
3
4
|
// The exit record of a dispatched lane child — the terminal `result` event a `claude -p` session
|
|
@@ -12,18 +13,10 @@ import { claude_result_event, type ClaudeResultEvent } from './claude-result-eve
|
|
|
12
13
|
// over; a transcript with no result event at all reads as `undefined`, which the classifier treats as
|
|
13
14
|
// an unreadable ending rather than guessing one.
|
|
14
15
|
|
|
15
|
-
function parse_line(line: string): unknown {
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(line)
|
|
18
|
-
} catch {
|
|
19
|
-
return undefined
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
16
|
function read_exit_record(transcript: string): ClaudeResultEvent | undefined {
|
|
24
17
|
return transcript
|
|
25
18
|
.split('\n')
|
|
26
|
-
.map((line) => claude_result_event.decode(
|
|
19
|
+
.map((line) => claude_result_event.decode(json_value.parse_or_undefined(line)))
|
|
27
20
|
.findLast((record) => record !== undefined)
|
|
28
21
|
}
|
|
29
22
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import { parseArgs } from 'node:util'
|
|
4
3
|
import { path_decision } from '#scripts/josh/path-decision'
|
|
4
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
5
|
import { backlog_budget, type BacklogAnswer, type BudgetInput } from './backlog-budget'
|
|
6
6
|
|
|
7
7
|
// The thin half of `josh backlog:budget` (joshuafolkken/kit#1632): read the loop's state off the
|
|
@@ -32,11 +32,7 @@ type ParsedValues = Partial<Record<OptionName, string | boolean>>
|
|
|
32
32
|
// A misspelled flag is refused rather than defaulted, the contract `path_decision.has_unknown_flag`
|
|
33
33
|
// states for the path-decided commands: an invocation nobody can read is never answered.
|
|
34
34
|
function read_arguments(argv: ReadonlyArray<string>): ParsedValues | undefined {
|
|
35
|
-
|
|
36
|
-
return parseArgs({ args: [...argv], options: OPTIONS, strict: true }).values
|
|
37
|
-
} catch {
|
|
38
|
-
return undefined
|
|
39
|
-
}
|
|
35
|
+
return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
function text_of(value: string | boolean | undefined): string | undefined {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { parseArgs } from 'node:util'
|
|
5
4
|
import { CONTEXT_CUT_THRESHOLD } from '#scripts/cost-runtime/context-cut-threshold'
|
|
6
5
|
import { hook_decision } from '#scripts/josh/hook-decision'
|
|
7
6
|
import { josh_command } from '#scripts/josh/josh-run'
|
|
8
7
|
import { lane_await, type AwaitState } from '#scripts/lane/lane-await'
|
|
9
8
|
import { lane_launch_cli } from '#scripts/lane/lane-launch-cli'
|
|
10
9
|
import { lane_registry } from '#scripts/lane/lane-registry'
|
|
10
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
11
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
11
12
|
import { run_carry, type RunCarry } from '#scripts/run/run-carry'
|
|
12
13
|
import { run_event_stream_emit } from '#scripts/run/run-event-stream-emit'
|
|
13
14
|
import { run_merge_cli } from '#scripts/run/run-merge-cli'
|
|
@@ -49,7 +50,7 @@ const LIST_SEPARATOR = ','
|
|
|
49
50
|
const COUNT_PATTERN = /^\d+$/u
|
|
50
51
|
const ISSUE_PATTERN = /^[1-9]\d*$/u
|
|
51
52
|
const USAGE =
|
|
52
|
-
'Usage: josh backlog:drive --owner <pid> [--active <ISO-8601>] [--max <n>] [--idle <minutes>] [--only] [--exclude <n>[,<n>...]] [--await <n>[,<n>...]] [--window <minutes>]'
|
|
53
|
+
'Usage: josh backlog:drive --owner <pid> [--active <ISO-8601>] [--max <n>] [--idle <minutes>] [--only] [--exclude <n>[,<n>...]] [--await <n>[,<n>...]] [--window <minutes>] [--stopped <n>]'
|
|
53
54
|
|
|
54
55
|
const OPTIONS = {
|
|
55
56
|
active: { type: 'string' },
|
|
@@ -59,6 +60,7 @@ const OPTIONS = {
|
|
|
59
60
|
max: { type: 'string' },
|
|
60
61
|
only: { type: 'boolean' },
|
|
61
62
|
owner: { type: 'string' },
|
|
63
|
+
stopped: { type: 'string' },
|
|
62
64
|
window: { type: 'string' },
|
|
63
65
|
} as const
|
|
64
66
|
|
|
@@ -72,16 +74,14 @@ interface DriveContext {
|
|
|
72
74
|
window_ms: number | undefined
|
|
73
75
|
window: string | undefined
|
|
74
76
|
is_only: boolean
|
|
77
|
+
// The child whose merge answered `stop` before the hand-back: the resumed run starts already stopping.
|
|
78
|
+
stopped: string | undefined
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
type Values = Partial<Record<Exclude<keyof typeof OPTIONS, 'only'>, string>> & { only?: boolean }
|
|
78
82
|
|
|
79
83
|
function read_values(argv: ReadonlyArray<string>): Values | undefined {
|
|
80
|
-
|
|
81
|
-
return parseArgs({ args: [...argv], options: OPTIONS, strict: true }).values
|
|
82
|
-
} catch {
|
|
83
|
-
return undefined
|
|
84
|
-
}
|
|
84
|
+
return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// A comma list of issue numbers; one entry that is not an issue number refuses the whole list.
|
|
@@ -110,6 +110,12 @@ function is_valid_window(raw: string | undefined): boolean {
|
|
|
110
110
|
return raw === undefined || COUNT_PATTERN.test(raw)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
function is_valid_single(values: Values): boolean {
|
|
114
|
+
const is_valid_stopped = values.stopped === undefined || ISSUE_PATTERN.test(values.stopped)
|
|
115
|
+
|
|
116
|
+
return is_valid_stopped && is_valid_window(values.window)
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
function owner_of(values: Values | undefined): string | undefined {
|
|
114
120
|
const owner = values?.owner
|
|
115
121
|
|
|
@@ -117,7 +123,7 @@ function owner_of(values: Values | undefined): string | undefined {
|
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
function to_context(values: Values, owner: string): DriveContext | undefined {
|
|
120
|
-
if (!
|
|
126
|
+
if (!is_valid_single(values)) return undefined
|
|
121
127
|
|
|
122
128
|
const forwarded = forwarded_of(values)
|
|
123
129
|
const exclude = to_issues(values.exclude)
|
|
@@ -136,6 +142,7 @@ function to_context(values: Values, owner: string): DriveContext | undefined {
|
|
|
136
142
|
window_ms,
|
|
137
143
|
window: values.window,
|
|
138
144
|
is_only: values.only === true,
|
|
145
|
+
stopped: values.stopped,
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
|
|
@@ -274,6 +281,7 @@ function resume_line(state: DriveState, context: DriveContext): string {
|
|
|
274
281
|
|
|
275
282
|
if (exclude.length > 0) flags.push('--exclude', exclude.join(LIST_SEPARATOR))
|
|
276
283
|
if (context.window !== undefined) flags.push('--window', context.window)
|
|
284
|
+
if (state.stopped_by !== undefined) flags.push('--stopped', state.stopped_by)
|
|
277
285
|
|
|
278
286
|
return `resume: ${flags.join(' ')}`
|
|
279
287
|
}
|
|
@@ -325,7 +333,9 @@ async function drive(context: DriveContext): Promise<number> {
|
|
|
325
333
|
const seeded = await seeded_lanes(context)
|
|
326
334
|
|
|
327
335
|
if (seeded === undefined) return FAILURE_EXIT_CODE
|
|
328
|
-
const
|
|
336
|
+
const fresh = backlog_drive.initial_state(seeded, context.active ?? new Date().toISOString())
|
|
337
|
+
const initial =
|
|
338
|
+
context.stopped === undefined ? fresh : backlog_drive.merge_stopped(context.stopped, fresh)
|
|
329
339
|
const last = { state: { ...initial, exclude: [...context.exclude] } }
|
|
330
340
|
const config = { poll_ms: POLL_MS, offer_ms: OFFER_MS, window_ms: context.window_ms }
|
|
331
341
|
const end = await backlog_drive.run_loop(last.state, config, ports_of(context, seeded, last))
|
|
@@ -340,7 +350,7 @@ async function run_safe(context: DriveContext): Promise<number> {
|
|
|
340
350
|
try {
|
|
341
351
|
return await drive(context)
|
|
342
352
|
} catch (error) {
|
|
343
|
-
console.info(`error ${
|
|
353
|
+
console.info(`error ${error_text.message_of(error)}`)
|
|
344
354
|
|
|
345
355
|
return FAILURE_EXIT_CODE
|
|
346
356
|
}
|
|
@@ -16,19 +16,22 @@
|
|
|
16
16
|
// **Everything it touches is a port**, the pattern `run-wake-loop.ts` set, so the sequencing is pinned
|
|
17
17
|
// without a process, a network or a clock.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const RUN_VERDICT = 'run'
|
|
20
|
+
const STOP_VERDICT = 'stop'
|
|
21
|
+
// The `run:merge` tokens that end the loop at once: a hand-off, a person's stop, the environment down,
|
|
22
|
+
// a foreign owner, an unreadable state. `resumed` is not among them — the same lane is awaited again.
|
|
23
|
+
const IMMEDIATE_TOKENS: ReadonlySet<string> = new Set([
|
|
22
24
|
'over',
|
|
23
25
|
'human-review',
|
|
24
|
-
'stop',
|
|
25
26
|
'environment',
|
|
26
27
|
'busy',
|
|
27
28
|
'retry',
|
|
28
29
|
])
|
|
30
|
+
// Every token `run:merge` prints first even on a non-zero exit. `stop` — a parked backlog or the failure
|
|
31
|
+
// guard — is the offer's `stop` reached through a merge: the child is collected, nothing more starts,
|
|
32
|
+
// and the children still in flight are collected before the loop ends (joshuafolkken/kit#2881).
|
|
33
|
+
const HANDOFF_TOKENS: ReadonlySet<string> = new Set([...IMMEDIATE_TOKENS, STOP_VERDICT])
|
|
29
34
|
const RESUMED_TOKEN = 'resumed'
|
|
30
|
-
const RUN_VERDICT = 'run'
|
|
31
|
-
const STOP_VERDICT = 'stop'
|
|
32
35
|
const WATCH_VERDICT = 'watch'
|
|
33
36
|
// The verdicts the loop acts on without the parent. Any other is handed back as it was printed.
|
|
34
37
|
const KNOWN_VERDICTS: ReadonlySet<string> = new Set([
|
|
@@ -63,6 +66,8 @@ interface DriveState {
|
|
|
63
66
|
is_stopping: boolean
|
|
64
67
|
stop_reason?: string | undefined
|
|
65
68
|
stop_is_finish?: boolean | undefined
|
|
69
|
+
// The child whose merge answered `stop`, carried on the resume line: no offer re-derives that stop.
|
|
70
|
+
stopped_by?: string | undefined
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
interface DriveEnd {
|
|
@@ -133,22 +138,43 @@ function initial_state(in_flight: ReadonlyArray<string>, active: string): DriveS
|
|
|
133
138
|
}
|
|
134
139
|
}
|
|
135
140
|
|
|
136
|
-
//
|
|
141
|
+
// The stop a merge reached: kept as the first stop's reason when an offer already stopped the run.
|
|
142
|
+
function merge_stopped(issue: string, state: DriveState): DriveState {
|
|
143
|
+
if (state.is_stopping) return state
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
...state,
|
|
147
|
+
is_stopping: true,
|
|
148
|
+
stop_reason: `run:merge #${issue} answered stop`,
|
|
149
|
+
stopped_by: issue,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// The child collected and excluded, and — unless its cut was resumed in place — out of flight.
|
|
154
|
+
function collected_state(
|
|
155
|
+
issue: string,
|
|
156
|
+
token: string,
|
|
157
|
+
state: DriveState,
|
|
158
|
+
active: string,
|
|
159
|
+
): DriveState {
|
|
160
|
+
const in_flight =
|
|
161
|
+
token === RESUMED_TOKEN ? state.in_flight : state.in_flight.filter((item) => item !== issue)
|
|
162
|
+
const exclude = state.exclude.includes(issue) ? state.exclude : [...state.exclude, issue]
|
|
163
|
+
|
|
164
|
+
return { ...state, in_flight, exclude, active }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// One finished child: handed back on an immediate token, otherwise collected.
|
|
137
168
|
async function collect(issue: string, state: DriveState, ports: DrivePorts): Promise<PassResult> {
|
|
138
169
|
const token = await ports.merge(issue)
|
|
139
170
|
|
|
140
171
|
// An empty first line is a `run:merge` that failed before printing one: never read as a collection.
|
|
141
172
|
if (token === '') return ended('merge', state, undefined, issue)
|
|
142
|
-
if (
|
|
173
|
+
if (IMMEDIATE_TOKENS.has(token)) return ended('merge', state, token, issue)
|
|
143
174
|
|
|
144
|
-
const
|
|
145
|
-
token === RESUMED_TOKEN ? state.in_flight : state.in_flight.filter((item) => item !== issue)
|
|
146
|
-
const exclude = state.exclude.includes(issue) ? state.exclude : [...state.exclude, issue]
|
|
175
|
+
const next = collected_state(issue, token, state, ports.now().toISOString())
|
|
147
176
|
|
|
148
|
-
return {
|
|
149
|
-
kind: 'continue',
|
|
150
|
-
state: { ...state, in_flight, exclude, active: ports.now().toISOString() },
|
|
151
|
-
}
|
|
177
|
+
return { kind: 'continue', state: token === STOP_VERDICT ? merge_stopped(issue, next) : next }
|
|
152
178
|
}
|
|
153
179
|
|
|
154
180
|
async function collect_finished(state: DriveState, ports: DrivePorts): Promise<PassResult> {
|
|
@@ -320,7 +346,15 @@ async function run_loop(
|
|
|
320
346
|
return step.end
|
|
321
347
|
}
|
|
322
348
|
|
|
323
|
-
const backlog_drive = {
|
|
349
|
+
const backlog_drive = {
|
|
350
|
+
HANDOFF_TOKENS,
|
|
351
|
+
collect,
|
|
352
|
+
initial_state,
|
|
353
|
+
merge_stopped,
|
|
354
|
+
on_verdict,
|
|
355
|
+
run_loop,
|
|
356
|
+
run_pass,
|
|
357
|
+
}
|
|
324
358
|
|
|
325
359
|
export type { DriveEnd, DrivePorts, DriveState, LoopConfig, LoopPorts, OfferRead, PassResult }
|
|
326
360
|
export { backlog_drive }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { parseArgs } from 'node:util'
|
|
4
4
|
import { josh_command } from '#scripts/josh/josh-run'
|
|
5
|
-
import {
|
|
5
|
+
import { rule_value_cache } from '#scripts/rules/rule-value-cache'
|
|
6
6
|
import { run_event_stream } from '#scripts/run/run-event-stream'
|
|
7
7
|
import { run_event_stream_emit } from '#scripts/run/run-event-stream-emit'
|
|
8
8
|
import { z } from 'zod'
|
|
@@ -242,11 +242,12 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
242
242
|
|
|
243
243
|
if (values === undefined) return refuse()
|
|
244
244
|
|
|
245
|
-
// The loop head is the one place `rule:value` is
|
|
245
|
+
// The loop head is the one place `rule:value` is read from: once per iteration, the parent's
|
|
246
246
|
// context still small, so a rule that never fires shows up as a printed row rather than as
|
|
247
|
-
// something a person has to remember to measure (joshuafolkken/kit#2271). It
|
|
248
|
-
//
|
|
249
|
-
|
|
247
|
+
// something a person has to remember to measure (joshuafolkken/kit#2271). It prints the cached
|
|
248
|
+
// reading and refreshes it in a detached process, so the offer never waits on the measurement
|
|
249
|
+
// (joshuafolkken/kit#2881), and it never fails, so a broken reading never stops the backlog.
|
|
250
|
+
rule_value_cache.emit()
|
|
250
251
|
|
|
251
252
|
const counts = counts_of(values)
|
|
252
253
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { EpicChild } from '#scripts/epic/epic-graph'
|
|
2
|
+
import type { IssueReference } from '#scripts/epic/epic-reference'
|
|
2
3
|
import { epic_report, type EpicNextResult } from '#scripts/epic/epic-report'
|
|
3
4
|
import { epic_solo } from '#scripts/epic/epic-solo'
|
|
4
5
|
import { epic_triage } from '#scripts/epic/epic-triage'
|
|
5
|
-
import type { IssueReference } from '#scripts/git/git-epic-reference'
|
|
6
6
|
import { has_label_name, IN_PROGRESS_LABEL, RUN_SOLO_LABEL } from '#scripts/git/issue-labels'
|
|
7
7
|
import type { OutOfScopeRow } from './backlog-scope'
|
|
8
8
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
4
5
|
import { build } from 'esbuild'
|
|
5
6
|
|
|
6
7
|
const PACKAGE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
@@ -26,7 +27,7 @@ async function main(): Promise<void> {
|
|
|
26
27
|
await build_bin()
|
|
27
28
|
console.info(` ✔ ${OUTFILE} built`)
|
|
28
29
|
} catch (error) {
|
|
29
|
-
console.error(
|
|
30
|
+
console.error(error_text.message_of(error))
|
|
30
31
|
process.exit(1)
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
3
4
|
import { build_library, library_paths } from './build-library'
|
|
4
5
|
|
|
5
6
|
const CONFIG_MERGE_LIBRARY = 'config-merge'
|
|
@@ -16,7 +17,7 @@ async function main(): Promise<void> {
|
|
|
16
17
|
console.info(` ✔ ${CONFIG_MERGE_OUTFILE} built`)
|
|
17
18
|
console.info(` ✔ ${CONFIG_MERGE_DTS_FILE} built`)
|
|
18
19
|
} catch (error) {
|
|
19
|
-
console.error(
|
|
20
|
+
console.error(error_text.message_of(error))
|
|
20
21
|
process.exit(1)
|
|
21
22
|
}
|
|
22
23
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
|
+
import { readdirSync, rmSync } from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
4
6
|
import { build } from 'esbuild'
|
|
5
7
|
|
|
6
8
|
const PACKAGE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
@@ -50,17 +52,35 @@ function outfile_for(bundle: HookBundle): string {
|
|
|
50
52
|
return path.join(OUT_DIR, `${bundle.out}.js`)
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
// The output directory is flat (every entry and chunk sits at its top level), so a file whose name
|
|
56
|
+
// this build did not emit is a leftover of an earlier one.
|
|
57
|
+
function prune_stale_outputs(out_directory: string, outputs: ReadonlyArray<string>): void {
|
|
58
|
+
const live = new Set(outputs.map((output) => path.basename(output)))
|
|
59
|
+
|
|
60
|
+
for (const name of readdirSync(out_directory)) {
|
|
61
|
+
if (!live.has(name)) rmSync(path.join(out_directory, name), { recursive: true, force: true })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// With `splitting: true` every build emits freshly hashed `chunk-*.js` files, so without a cleanup
|
|
66
|
+
// the chunks of every earlier build pile up beside the live ones and ship in any local pack
|
|
67
|
+
// (joshuafolkken/kit#2885). The cleanup runs after the build rather than emptying the directory
|
|
68
|
+
// first: this checkout's own settings.json launches `node dist/hooks/<name>.js` on every tool call,
|
|
69
|
+
// and an emptied directory would make a hook launched mid-build fail to load and its guard fail open.
|
|
70
|
+
async function build_hooks(out_directory: string = OUT_DIR): Promise<void> {
|
|
71
|
+
const { metafile } = await build({
|
|
55
72
|
entryPoints: HOOK_BUNDLES.map((bundle) => entry_point(bundle)),
|
|
56
|
-
outdir:
|
|
73
|
+
outdir: out_directory,
|
|
57
74
|
bundle: true,
|
|
58
75
|
splitting: true,
|
|
59
76
|
platform: 'node',
|
|
60
77
|
format: 'esm',
|
|
61
78
|
target: 'node22',
|
|
62
79
|
packages: 'external',
|
|
80
|
+
metafile: true,
|
|
63
81
|
})
|
|
82
|
+
|
|
83
|
+
prune_stale_outputs(out_directory, Object.keys(metafile.outputs))
|
|
64
84
|
}
|
|
65
85
|
|
|
66
86
|
async function main(): Promise<void> {
|
|
@@ -68,7 +88,7 @@ async function main(): Promise<void> {
|
|
|
68
88
|
await build_hooks()
|
|
69
89
|
for (const bundle of HOOK_BUNDLES) console.info(` ✔ ${outfile_for(bundle)} built`)
|
|
70
90
|
} catch (error) {
|
|
71
|
-
console.error(
|
|
91
|
+
console.error(error_text.message_of(error))
|
|
72
92
|
process.exit(1)
|
|
73
93
|
}
|
|
74
94
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
3
4
|
import { build_library, library_paths } from './build-library'
|
|
4
5
|
|
|
5
6
|
const MANAGED_MARKER_LIBRARY = 'managed-marker'
|
|
@@ -16,7 +17,7 @@ async function main(): Promise<void> {
|
|
|
16
17
|
console.info(` ✔ ${MANAGED_MARKER_OUTFILE} built`)
|
|
17
18
|
console.info(` ✔ ${MANAGED_MARKER_DTS_FILE} built`)
|
|
18
19
|
} catch (error) {
|
|
19
|
-
console.error(
|
|
20
|
+
console.error(error_text.message_of(error))
|
|
20
21
|
process.exit(1)
|
|
21
22
|
}
|
|
22
23
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
3
4
|
import { build_library, library_paths } from './build-library'
|
|
4
5
|
|
|
5
6
|
const SELF_SYNC_GUARD_LIBRARY = 'self-sync-guard'
|
|
@@ -16,7 +17,7 @@ async function main(): Promise<void> {
|
|
|
16
17
|
console.info(` ✔ ${SELF_SYNC_GUARD_OUTFILE} built`)
|
|
17
18
|
console.info(` ✔ ${SELF_SYNC_GUARD_DTS_FILE} built`)
|
|
18
19
|
} catch (error) {
|
|
19
|
-
console.error(
|
|
20
|
+
console.error(error_text.message_of(error))
|
|
20
21
|
process.exit(1)
|
|
21
22
|
}
|
|
22
23
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
3
4
|
import { build_library, library_paths } from './build-library'
|
|
4
5
|
|
|
5
6
|
const VERSION_LIBRARY = 'version'
|
|
@@ -15,7 +16,7 @@ async function main(): Promise<void> {
|
|
|
15
16
|
console.info(` ✔ ${VERSION_OUTFILE} built`)
|
|
16
17
|
console.info(` ✔ ${VERSION_DTS_FILE} built`)
|
|
17
18
|
} catch (error) {
|
|
18
|
-
console.error(
|
|
19
|
+
console.error(error_text.message_of(error))
|
|
19
20
|
process.exit(1)
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
2
2
|
|
|
3
3
|
// `josh cases <path...>` (joshuafolkken/kit#2246): read the changed paths and answer which I/O
|
|
4
4
|
// boundaries the code crosses — network, process start, filesystem — then name the abnormal cases
|
|
@@ -63,11 +63,7 @@ const BOUNDARIES: ReadonlyArray<BoundaryDefinition> = [
|
|
|
63
63
|
// A path that cannot be read counts as no boundary rather than an error: a deleted or renamed path in
|
|
64
64
|
// a diff is still passed in, and the answer for it is simply "nothing crossed here".
|
|
65
65
|
function read_file(path: string): string {
|
|
66
|
-
|
|
67
|
-
return readFileSync(path, 'utf8')
|
|
68
|
-
} catch {
|
|
69
|
-
return ''
|
|
70
|
-
}
|
|
66
|
+
return file_reader.read_if_readable(path) ?? ''
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
// The distinct boundaries any of the paths cross. The content is joined and each boundary is tested
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { git_gh_exec } from '#scripts/git/git-gh-exec'
|
|
3
4
|
import {
|
|
4
5
|
BREAKING_CHANGE_LABEL as BREAKING,
|
|
5
6
|
BUGFIX_LABEL as BUGFIX,
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
ENHANCEMENT_LABEL,
|
|
8
9
|
type ReleaseClassification,
|
|
9
10
|
} from '#scripts/git/issue-labels'
|
|
10
|
-
import {
|
|
11
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
11
12
|
import { z } from 'zod'
|
|
12
13
|
|
|
13
14
|
const CLASSIFICATION_SET: ReadonlySet<string> = new Set(CLASSIFICATION_LABELS)
|
|
@@ -95,17 +96,17 @@ function select_issue_classification(issue_json: string): ReleaseClassification
|
|
|
95
96
|
// payload, that run fails, and when it lands in the newer check suite GitHub keeps its red result —
|
|
96
97
|
// a rerun replays the same payload and fails again (joshuafolkken/kit#2712).
|
|
97
98
|
function fetch_current_labels(repository: string, pull_number: number): ReadonlyArray<string> {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
if (result.exitCode !== 0) {
|
|
105
|
-
throw new Error(`Could not read the pull request labels: ${result.stderr}`)
|
|
106
|
-
}
|
|
99
|
+
const path = `repos/${repository}/issues/${String(pull_number)}/labels`
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const names = git_gh_exec.exec_gh_api_sync({ path, jq_filter: '.[].name' })
|
|
107
103
|
|
|
108
|
-
|
|
104
|
+
return names.split('\n').filter((name) => name !== '')
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const detail = error_text.message_of(error)
|
|
107
|
+
|
|
108
|
+
throw new Error(`Could not read the pull request labels: ${detail}`, { cause: error })
|
|
109
|
+
}
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
function check_event(
|
|
@@ -133,6 +134,7 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
|
133
134
|
const pr_classification = {
|
|
134
135
|
classification_error,
|
|
135
136
|
check_event,
|
|
137
|
+
fetch_current_labels,
|
|
136
138
|
declaration,
|
|
137
139
|
is_classification,
|
|
138
140
|
select_issue_classification,
|
|
@@ -2,6 +2,7 @@ import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { lane_paths } from '#scripts/lane/lane-paths'
|
|
5
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
5
6
|
import { cost_usage, type UsageRecord } from './cost-usage'
|
|
6
7
|
|
|
7
8
|
// Finding and reading Claude Code's session transcripts (joshuafolkken/kit#962).
|
|
@@ -404,16 +405,8 @@ function tally(content: string): Omit<SessionUsage, 'session_id'> {
|
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
|
|
407
|
-
function read_text(file_path: string): string | undefined {
|
|
408
|
-
try {
|
|
409
|
-
return readFileSync(file_path, 'utf8')
|
|
410
|
-
} catch {
|
|
411
|
-
return undefined
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
408
|
function read_session(file: SessionFile): SessionUsage {
|
|
416
|
-
const content =
|
|
409
|
+
const content = file_reader.read_if_readable(file.path)
|
|
417
410
|
|
|
418
411
|
if (content === undefined) return { session_id: file.session_id, ...empty_session() }
|
|
419
412
|
|
|
@@ -426,7 +419,7 @@ function read_session(file: SessionFile): SessionUsage {
|
|
|
426
419
|
// "could not be read" is already reported through `is_readable`, and throwing here would turn one
|
|
427
420
|
// missing session into a failed command.
|
|
428
421
|
function read_raw(file: SessionFile): string {
|
|
429
|
-
return
|
|
422
|
+
return file_reader.read_if_readable(file.path) ?? ''
|
|
430
423
|
}
|
|
431
424
|
|
|
432
425
|
// The same read, with the failure left visible (joshuafolkken/kit#1439). **An unreadable transcript
|
|
@@ -435,7 +428,7 @@ function read_raw(file: SessionFile): string {
|
|
|
435
428
|
// measured* rather than as measured at zero. `is_readable` on `SessionUsage` is the same distinction
|
|
436
429
|
// on the cost side; this is it for a caller that wants the text.
|
|
437
430
|
function read_optional(file: SessionFile): string | undefined {
|
|
438
|
-
return
|
|
431
|
+
return file_reader.read_if_readable(file.path)
|
|
439
432
|
}
|
|
440
433
|
|
|
441
434
|
// One directory the search looked in, and whether it was there to look in. `exists` tells a directory
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
3
4
|
import { read_spawn_stdout } from '#scripts/lib/spawn-exit'
|
|
4
5
|
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
5
6
|
import { safe_json_parse } from '#scripts/version/parse-json'
|
|
@@ -36,16 +37,8 @@ const STATE_TARGET_MISSING = 'target-missing'
|
|
|
36
37
|
const STATE_MISSING = 'missing'
|
|
37
38
|
type ClaudeMdState = typeof STATE_OK | typeof STATE_TARGET_MISSING | typeof STATE_MISSING
|
|
38
39
|
|
|
39
|
-
function read_optional(file_path: string): string | undefined {
|
|
40
|
-
try {
|
|
41
|
-
return existsSync(file_path) ? readFileSync(file_path, 'utf8') : undefined
|
|
42
|
-
} catch {
|
|
43
|
-
return undefined
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
function parse_manifest(root: string): ManifestShape | undefined {
|
|
48
|
-
const content =
|
|
41
|
+
const content = file_reader.read_if_readable(path.join(root, 'package.json'))
|
|
49
42
|
if (content === undefined) return undefined
|
|
50
43
|
const parsed = manifest_schema.safeParse(safe_json_parse(content))
|
|
51
44
|
|
|
@@ -74,7 +67,7 @@ function is_kit_consumer(root: string): boolean {
|
|
|
74
67
|
}
|
|
75
68
|
|
|
76
69
|
function is_plugin_declared(root: string): boolean {
|
|
77
|
-
const content =
|
|
70
|
+
const content = file_reader.read_if_readable(path.join(root, SETTINGS_PATH))
|
|
78
71
|
if (content === undefined) return false
|
|
79
72
|
const parsed = settings_schema.safeParse(safe_json_parse(content))
|
|
80
73
|
|
|
@@ -94,7 +87,7 @@ function configured_hooks_path(root: string): string | undefined {
|
|
|
94
87
|
}
|
|
95
88
|
|
|
96
89
|
function claude_md_state(root: string): ClaudeMdState {
|
|
97
|
-
const content =
|
|
90
|
+
const content = file_reader.read_if_readable(path.join(root, CLAUDE_MD))
|
|
98
91
|
if (content === undefined) return STATE_MISSING
|
|
99
92
|
if (!content.includes(CLAUDE_MD_POINTER)) return STATE_OK
|
|
100
93
|
|