@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,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
2
|
+
import { EPIC_LABEL } from '#scripts/git/issue-labels'
|
|
3
|
+
import { epic_body } from './epic-body'
|
|
4
|
+
import { epic_promote } from './epic-promote'
|
|
5
|
+
import { epic_relations } from './epic-relations'
|
|
6
|
+
import { epic_validate, type EpicSubject } from './epic-validate'
|
|
7
7
|
|
|
8
8
|
const EPIC_LABEL_COLOR = '#5319e7'
|
|
9
9
|
const EPIC_LABEL_DESCRIPTION = 'Tracks a batch of child issues from one split'
|
|
@@ -20,18 +20,18 @@ interface CreateEpicInput {
|
|
|
20
20
|
|
|
21
21
|
// The relations the declared order implies, applied through the shared module `--add` also uses so
|
|
22
22
|
// the two cannot drift. A failure is reported and stepped over rather than aborting a batch that is
|
|
23
|
-
// otherwise fine — see `
|
|
23
|
+
// otherwise fine — see `epic-relations.ts` for why the relation is not part of the contract.
|
|
24
24
|
async function apply_dependencies(input: {
|
|
25
25
|
children: ReadonlyArray<number>
|
|
26
26
|
is_ordered: boolean
|
|
27
27
|
}): Promise<void> {
|
|
28
|
-
const pairs =
|
|
28
|
+
const pairs = epic_body.build_dependency_pairs(input.children, input.is_ordered)
|
|
29
29
|
if (pairs.length === 0) return
|
|
30
30
|
|
|
31
|
-
const failures = await
|
|
31
|
+
const failures = await epic_relations.apply_relations(pairs, 'record')
|
|
32
32
|
|
|
33
33
|
console.info(
|
|
34
|
-
|
|
34
|
+
epic_relations.format_relation_report({
|
|
35
35
|
links: pairs,
|
|
36
36
|
failures,
|
|
37
37
|
action: 'record',
|
|
@@ -61,7 +61,7 @@ async function fill_run_command(
|
|
|
61
61
|
): Promise<void> {
|
|
62
62
|
if (epic_number === undefined) {
|
|
63
63
|
console.info(
|
|
64
|
-
`⚠️ Could not read the new epic's number from its URL; the Execution section still says \`${
|
|
64
|
+
`⚠️ Could not read the new epic's number from its URL; the Execution section still says \`${epic_body.format_run_command(undefined)}\`.`,
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
return
|
|
@@ -70,7 +70,7 @@ async function fill_run_command(
|
|
|
70
70
|
try {
|
|
71
71
|
await git_gh_command.issue_edit_body(
|
|
72
72
|
String(epic_number),
|
|
73
|
-
|
|
73
|
+
epic_body.build_epic_body({
|
|
74
74
|
children: input.children,
|
|
75
75
|
rationale: input.rationale,
|
|
76
76
|
is_ordered: input.is_ordered,
|
|
@@ -90,7 +90,7 @@ async function create_epic(input: CreateEpicInput): Promise<number> {
|
|
|
90
90
|
description: EPIC_LABEL_DESCRIPTION,
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
const body =
|
|
93
|
+
const body = epic_body.build_epic_body({
|
|
94
94
|
children: input.children,
|
|
95
95
|
rationale: input.rationale,
|
|
96
96
|
is_ordered: input.is_ordered,
|
|
@@ -107,7 +107,7 @@ async function create_epic(input: CreateEpicInput): Promise<number> {
|
|
|
107
107
|
console.info(`📋 Created epic: ${url}`)
|
|
108
108
|
await apply_dependencies({ children: input.children, is_ordered: input.is_ordered })
|
|
109
109
|
await fill_run_command(epic_number, input)
|
|
110
|
-
console.info(`▶ Run the children with: ${
|
|
110
|
+
console.info(`▶ Run the children with: ${epic_body.format_run_command(epic_number)}`)
|
|
111
111
|
|
|
112
112
|
return SUCCESS_EXIT_CODE
|
|
113
113
|
}
|
|
@@ -123,7 +123,7 @@ interface PromoteEpicInput {
|
|
|
123
123
|
// The issue being promoted, or the reason it cannot be. Refused when it already carries the epic
|
|
124
124
|
// sections — a second append would give the auto-close two task lists to disagree about.
|
|
125
125
|
async function read_promotion_subject(epic_number: number): Promise<EpicSubject | undefined> {
|
|
126
|
-
const subject =
|
|
126
|
+
const subject = epic_validate.parse_epic_subject(
|
|
127
127
|
await git_gh_command.issue_get_labels_and_body(String(epic_number)),
|
|
128
128
|
)
|
|
129
129
|
|
|
@@ -133,8 +133,8 @@ async function read_promotion_subject(epic_number: number): Promise<EpicSubject
|
|
|
133
133
|
return undefined
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
if (
|
|
137
|
-
const reason =
|
|
136
|
+
if (epic_promote.has_conflicting_tracking(subject.body)) {
|
|
137
|
+
const reason = epic_promote.conflict_reason(subject.body)
|
|
138
138
|
|
|
139
139
|
console.error(`✖ Cannot promote #${String(epic_number)}: ${reason}.`)
|
|
140
140
|
|
|
@@ -144,16 +144,16 @@ async function read_promotion_subject(epic_number: number): Promise<EpicSubject
|
|
|
144
144
|
return subject
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
// Write the promoted body and apply the label. The label is what `
|
|
147
|
+
// Write the promoted body and apply the label. The label is what `epic-close.ts` filters on, so
|
|
148
148
|
// an unlabelled issue is one the auto-close skips forever — a failure there is reported rather than
|
|
149
149
|
// logged past.
|
|
150
150
|
async function write_promotion(
|
|
151
151
|
input: PromoteEpicInput,
|
|
152
152
|
existing_body: string | undefined,
|
|
153
153
|
): Promise<number> {
|
|
154
|
-
const body =
|
|
154
|
+
const body = epic_promote.build_promoted_body({ ...input, body: existing_body })
|
|
155
155
|
|
|
156
|
-
const tracking_error =
|
|
156
|
+
const tracking_error = epic_promote.find_tracking_error(body, input.children)
|
|
157
157
|
|
|
158
158
|
if (tracking_error !== undefined) {
|
|
159
159
|
console.error(`✖ ${tracking_error}`)
|
|
@@ -190,14 +190,14 @@ async function promote_epic(input: PromoteEpicInput): Promise<number> {
|
|
|
190
190
|
|
|
191
191
|
console.info(`📋 Promoted #${String(input.epic_number)} to an epic.`)
|
|
192
192
|
await apply_dependencies({ children: input.children, is_ordered: input.is_ordered })
|
|
193
|
-
console.info(`▶ Run the children with: ${
|
|
193
|
+
console.info(`▶ Run the children with: ${epic_body.format_run_command(input.epic_number)}`)
|
|
194
194
|
|
|
195
195
|
return SUCCESS_EXIT_CODE
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
async function check_epic(epic_number: number): Promise<number> {
|
|
199
199
|
const raw = await git_gh_command.issue_get_labels_and_body(String(epic_number))
|
|
200
|
-
const subject =
|
|
200
|
+
const subject = epic_validate.parse_epic_subject(raw)
|
|
201
201
|
|
|
202
202
|
if (subject === undefined) {
|
|
203
203
|
console.error(`✖ Could not read issue #${String(epic_number)}.`)
|
|
@@ -205,19 +205,19 @@ async function check_epic(epic_number: number): Promise<number> {
|
|
|
205
205
|
return FAILURE_EXIT_CODE
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
const results =
|
|
208
|
+
const results = epic_validate.validate_epic(subject)
|
|
209
209
|
|
|
210
|
-
console.info(
|
|
210
|
+
console.info(epic_validate.format_check_report(subject.number, results))
|
|
211
211
|
|
|
212
|
-
return
|
|
212
|
+
return epic_validate.is_epic_valid(results) ? SUCCESS_EXIT_CODE : FAILURE_EXIT_CODE
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
const
|
|
215
|
+
const epic_run = {
|
|
216
216
|
parse_created_number,
|
|
217
217
|
create_epic,
|
|
218
218
|
promote_epic,
|
|
219
219
|
check_epic,
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
export {
|
|
222
|
+
export { epic_run, EPIC_LABEL_COLOR, EPIC_LABEL_DESCRIPTION, FAILURE_EXIT_CODE }
|
|
223
223
|
export type { CreateEpicInput, PromoteEpicInput }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { epic_parse } from './epic-parse'
|
|
2
2
|
|
|
3
3
|
// Locating a `## <Name>` section inside an epic body, as the shape every rewrite of one needs.
|
|
4
4
|
//
|
|
5
|
-
// It was `
|
|
5
|
+
// It was `epic-add-body.ts`'s private half: that file finds the `Dependencies` section so a
|
|
6
6
|
// declaration is replaced inside it rather than wherever a declaration-shaped line happens to sit.
|
|
7
7
|
// `## Decisions` needs the identical search, and a second copy of it is the clone `CLAUDE.md`
|
|
8
8
|
// prohibits — the fence mask especially, since a heading inside a fenced block is an illustration and
|
|
@@ -29,13 +29,13 @@ interface SectionRange {
|
|
|
29
29
|
end: number
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// Accepts the body or the lines it was already split into: `
|
|
32
|
+
// Accepts the body or the lines it was already split into: `epic-add-body.ts` holds both forms at
|
|
33
33
|
// different points of one rewrite, and `join`/`split` round-trip exactly, so one function serves both
|
|
34
34
|
// rather than the caller re-deriving a mask that has to agree with this one.
|
|
35
35
|
function to_body_lines(source: string | ReadonlyArray<string>): BodyLines {
|
|
36
36
|
const lines = typeof source === 'string' ? source.split('\n') : source
|
|
37
37
|
|
|
38
|
-
return { lines, mask:
|
|
38
|
+
return { lines, mask: epic_parse.fence_mask(lines.join('\n')) }
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function is_readable(input: BodyLines, index: number): boolean {
|
|
@@ -87,12 +87,12 @@ function is_in_range(index: number, range: SectionRange): boolean {
|
|
|
87
87
|
return index >= range.start && index < range.end
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const
|
|
90
|
+
const epic_sections = {
|
|
91
91
|
to_body_lines,
|
|
92
92
|
find_indices,
|
|
93
93
|
find_section_range,
|
|
94
94
|
is_in_range,
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
export {
|
|
97
|
+
export { epic_sections }
|
|
98
98
|
export type { BodyLines, SectionRange }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { EPIC_LABEL } from '#scripts/git/issue-labels'
|
|
2
|
+
import { epic_parse } from './epic-parse'
|
|
3
|
+
import { to_issue_reference } from './epic-reference'
|
|
4
4
|
|
|
5
5
|
// Whether an issue is an epic this repository's writers may edit, and why it is not when it is not.
|
|
6
6
|
//
|
|
@@ -47,7 +47,7 @@ function find_epic_shape_error(subject: EpicShapeSubject, body: string): string
|
|
|
47
47
|
return `${reference} does not carry the \`${EPIC_LABEL}\` label, so it is not an epic. ${promote_remedy(subject.epic_number)}`
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
if (
|
|
50
|
+
if (epic_parse.has_external_task_list_entry(body)) {
|
|
51
51
|
return `${reference} tracks a child in another repository; editing the declared order of a cross-repository epic is joshuafolkken/kit#864's scope, not this command's.`
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -68,14 +68,14 @@ function find_subject_error(
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// A declaration an edit can be relative to: exactly one of the two machine-readable forms.
|
|
71
|
-
return
|
|
71
|
+
return epic_parse.has_machine_readable_declaration(subject.body)
|
|
72
72
|
? undefined
|
|
73
73
|
: missing_declaration_error(subject.epic_number)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
const
|
|
76
|
+
const epic_shape = {
|
|
77
77
|
find_subject_error,
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export {
|
|
80
|
+
export { epic_shape }
|
|
81
81
|
export type { EpicShapeSubject }
|
|
@@ -21,12 +21,11 @@ import { epic_report, type EpicNextResult } from './epic-report'
|
|
|
21
21
|
// it, so it is not starved by the lanes it is waiting on. A listing that could not be read is not an
|
|
22
22
|
// idle repository — the same fail-safe answer `epic_busy` gives.
|
|
23
23
|
//
|
|
24
|
-
// **
|
|
25
|
-
//
|
|
26
|
-
// path
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// the ones ahead of it keep their lanes.
|
|
24
|
+
// **The label does not move a candidate up the ranking** (joshuafolkken/kit#2928, reversing
|
|
25
|
+
// joshuafolkken/kit#2778). `run:solo` means "runs alone", and tidying that merely touches the
|
|
26
|
+
// verification path carries it as well as a defect that breaks it. Which of them goes first is the
|
|
27
|
+
// ranking's question — `issue-rank.ts` puts a verification-path defect at the head — so an idle
|
|
28
|
+
// repository takes the candidates in rank order like any other.
|
|
30
29
|
|
|
31
30
|
const SOLO_LABELS: ReadonlySet<string> = new Set([RUN_SOLO_LABEL])
|
|
32
31
|
|
|
@@ -75,32 +74,21 @@ function none_of(children: ReadonlyArray<EpicChild>, notice: string): SoloSelect
|
|
|
75
74
|
return { offered: [], withheld: [...children], notice }
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
// The ranking with the first `run:solo` candidate moved to the head — but only into an idle
|
|
79
|
-
// repository, the one place it may start. Anywhere else the ranking is returned as it was.
|
|
80
|
-
function prefer(children: ReadonlyArray<EpicChild>, read: BusyRead): ReadonlyArray<EpicChild> {
|
|
81
|
-
const solo = children.find((child) => is_solo(child))
|
|
82
|
-
|
|
83
|
-
if (solo === undefined || read.kind !== 'idle') return children
|
|
84
|
-
|
|
85
|
-
return [solo, ...children.filter((child) => child !== solo)]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
77
|
// Where the first `run:solo` candidate sits decides what starts: everything when there is none, the
|
|
89
|
-
// candidates ahead of it when
|
|
78
|
+
// candidates ahead of it when it is not first, and it alone once it heads an idle repository's ranking.
|
|
90
79
|
function select_candidates(
|
|
91
80
|
children: ReadonlyArray<EpicChild>,
|
|
92
81
|
read: BusyRead,
|
|
93
82
|
repo: string,
|
|
94
83
|
): SoloSelection {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const solo = ordered[index]
|
|
84
|
+
const index = children.findIndex((child) => is_solo(child))
|
|
85
|
+
const solo = children[index]
|
|
98
86
|
|
|
99
|
-
if (solo === undefined) return first_of(
|
|
100
|
-
if (index > 0) return first_of(
|
|
101
|
-
if (read.kind === 'idle') return first_of(
|
|
87
|
+
if (solo === undefined) return first_of(children, children.length)
|
|
88
|
+
if (index > 0) return first_of(children, index)
|
|
89
|
+
if (read.kind === 'idle') return first_of(children, 1)
|
|
102
90
|
|
|
103
|
-
return none_of(
|
|
91
|
+
return none_of(children, waiting_message(solo.number, repo))
|
|
104
92
|
}
|
|
105
93
|
|
|
106
94
|
// An unreadable or truncated listing keeps nothing: without it a running `run:solo` issue cannot be
|
|
@@ -129,11 +117,24 @@ function withhold(result: EpicNextResult, repo: string, selection: SoloSelection
|
|
|
129
117
|
}
|
|
130
118
|
}
|
|
131
119
|
|
|
120
|
+
// What decides the selection. `select` by default; `backlog:next` passes one that ranks before it and
|
|
121
|
+
// caps after it (`backlog-rank.ts`), so the gate itself is written once.
|
|
122
|
+
type SoloSelector = (
|
|
123
|
+
children: ReadonlyArray<EpicChild>,
|
|
124
|
+
read: BusyRead,
|
|
125
|
+
repo: string,
|
|
126
|
+
) => SoloSelection
|
|
127
|
+
|
|
132
128
|
// Only a `run` answer offers anything, so every other verdict passes through untouched.
|
|
133
|
-
function gate(
|
|
129
|
+
function gate(
|
|
130
|
+
result: EpicNextResult,
|
|
131
|
+
read: BusyRead,
|
|
132
|
+
repo: string,
|
|
133
|
+
selector: SoloSelector = select,
|
|
134
|
+
): SoloGate {
|
|
134
135
|
if (result.verdict !== 'run') return { result }
|
|
135
136
|
|
|
136
|
-
const selection =
|
|
137
|
+
const selection = selector(epic_report.candidates_for_repo(result, repo), read, repo)
|
|
137
138
|
const gated = withhold(result, repo, selection)
|
|
138
139
|
|
|
139
140
|
return selection.notice === undefined
|
|
@@ -141,7 +142,7 @@ function gate(result: EpicNextResult, read: BusyRead, repo: string): SoloGate {
|
|
|
141
142
|
: { result: gated, notice: selection.notice }
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
const epic_solo = { gate, is_solo,
|
|
145
|
+
const epic_solo = { gate, is_solo, select }
|
|
145
146
|
|
|
146
|
-
export type { SoloGate, SoloSelection }
|
|
147
|
+
export type { SoloGate, SoloSelection, SoloSelector }
|
|
147
148
|
export { epic_solo }
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { EPIC_LABEL, has_label_name } from '#scripts/git/issue-labels'
|
|
2
|
+
import { parse_json_object_safe } from '#scripts/git/parse-json-array'
|
|
3
|
+
import { epic_subject_schema } from '#scripts/git/schemas'
|
|
4
|
+
import { epic_parse, type DeclarationState } from './epic-parse'
|
|
5
5
|
|
|
6
6
|
// The four requirements an epic has to satisfy are otherwise enforced only by an agent reading the
|
|
7
7
|
// procedure carefully, and three of the four fail silently: a missing label or a bare `#N` child
|
|
8
|
-
// list makes `
|
|
8
|
+
// list makes `epic-close.ts` skip the epic forever, and prose dependencies make the batch read
|
|
9
9
|
// as unordered so a skipped `--add-blocked-by` is never reported. Checking them here reuses
|
|
10
|
-
// `
|
|
10
|
+
// `epic-parse.ts` rather than restating the patterns, so "what the auto-close can read" and
|
|
11
11
|
// "what this check accepts" are one definition instead of two that can drift.
|
|
12
12
|
|
|
13
13
|
interface EpicSubject {
|
|
14
14
|
number: number
|
|
15
15
|
// The epic's own state, as GitHub returned it (`OPEN` / `CLOSED`, casing unnormalized). Only
|
|
16
|
-
// `
|
|
16
|
+
// `epic-add.ts` reads it — to refuse adding a child to a closed epic — so it is optional: a read
|
|
17
17
|
// that came back without the field leaves it undefined, which the refusal treats as "not confirmed
|
|
18
18
|
// closed" rather than blocking (joshuafolkken/kit#2337).
|
|
19
19
|
state?: string | undefined
|
|
@@ -65,7 +65,7 @@ function format_children(children: ReadonlyArray<number>): string {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function check_task_list(subject: EpicSubject): CheckResult {
|
|
68
|
-
const children =
|
|
68
|
+
const children = epic_parse.parse_task_list_issue_numbers(subject.body)
|
|
69
69
|
const is_passing = children.length > 0
|
|
70
70
|
|
|
71
71
|
return {
|
|
@@ -94,11 +94,11 @@ function describe_dependencies(state: DeclarationState): string {
|
|
|
94
94
|
// each of them. The literal is compared against the constant the generator emits, and the chain
|
|
95
95
|
// against the one the link reader parses, so no two of them can drift into disagreement.
|
|
96
96
|
function check_dependencies(subject: EpicSubject): CheckResult {
|
|
97
|
-
const state =
|
|
97
|
+
const state = epic_parse.read_declaration(subject.body)
|
|
98
98
|
|
|
99
99
|
return {
|
|
100
100
|
name: 'dependencies section',
|
|
101
|
-
is_passing:
|
|
101
|
+
is_passing: epic_parse.is_declaration_readable(state),
|
|
102
102
|
detail: describe_dependencies(state),
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -107,7 +107,7 @@ function check_dependencies(subject: EpicSubject): CheckResult {
|
|
|
107
107
|
// deliberately leaves such an epic open. Reporting it here is what turns "stayed open silently"
|
|
108
108
|
// into "was told to close it manually".
|
|
109
109
|
function check_external_children(subject: EpicSubject): CheckResult {
|
|
110
|
-
const has_external =
|
|
110
|
+
const has_external = epic_parse.has_external_task_list_entry(subject.body)
|
|
111
111
|
|
|
112
112
|
return {
|
|
113
113
|
name: 'auto-close eligibility',
|
|
@@ -144,12 +144,12 @@ function format_check_report(epic_number: number, results: ReadonlyArray<CheckRe
|
|
|
144
144
|
return [...lines, '', summary].join('\n')
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
const
|
|
147
|
+
const epic_validate = {
|
|
148
148
|
parse_epic_subject,
|
|
149
149
|
validate_epic,
|
|
150
150
|
is_epic_valid,
|
|
151
151
|
format_check_report,
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
export {
|
|
154
|
+
export { epic_validate }
|
|
155
155
|
export type { CheckResult, EpicSubject }
|
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Create an epic Issue that satisfies every requirement the auto-close and the order warning read.
|
|
4
4
|
*
|
|
5
|
-
* Usage: tsx scripts
|
|
5
|
+
* Usage: tsx scripts/epic/epic.ts "<title>" <N1> <N2> ... [--ordered] [--rationale-file <path|->]
|
|
6
6
|
* [--origin <owner/repo#N>]
|
|
7
|
-
* tsx scripts
|
|
8
|
-
* tsx scripts
|
|
7
|
+
* tsx scripts/epic/epic.ts --promote <N> <N1> <N2> ... [same flags]
|
|
8
|
+
* tsx scripts/epic/epic.ts --add <E> <N1> <N2> ... [--before <M> | --after <M>]
|
|
9
9
|
* [--decision-file <path|->]
|
|
10
|
-
* tsx scripts
|
|
10
|
+
* tsx scripts/epic/epic.ts --remove <E> <M> <N> ... [--decision-file <path|->]
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { git_epic_run } from '../scripts/git/git-epic-run'
|
|
16
|
-
import { git_gh_command } from '../scripts/git/git-gh-command'
|
|
12
|
+
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
13
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
14
|
+
import { epic_add, type AddChildrenInput } from './epic-add'
|
|
17
15
|
import { epic_cli, type AddArguments, type CrossRepoAddTarget } from './epic-cli'
|
|
16
|
+
import { epic_reconcile } from './epic-reconcile'
|
|
17
|
+
import { epic_remove } from './epic-remove'
|
|
18
|
+
import { epic_run } from './epic-run'
|
|
18
19
|
|
|
19
20
|
const ARGV_OFFSET = 2
|
|
20
21
|
const FLAGS = '[--ordered] [--rationale-file <path|->] [--origin <owner/repo#N>]'
|
|
@@ -37,7 +38,7 @@ async function run_promotion(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
37
38
|
return FAILURE_EXIT_CODE
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
return await
|
|
41
|
+
return await epic_run.promote_epic({
|
|
41
42
|
epic_number: parsed.epic_number,
|
|
42
43
|
children: parsed.children,
|
|
43
44
|
rationale: epic_cli.read_rationale(parsed.rationale_path),
|
|
@@ -67,7 +68,7 @@ function to_add_input(parsed: AddArguments): AddChildrenInput {
|
|
|
67
68
|
async function run_qualified_addition(found: CrossRepoAddTarget): Promise<number> {
|
|
68
69
|
const local = epic_cli.resolve_local_add(found, await git_gh_command.repo_get_name_with_owner())
|
|
69
70
|
|
|
70
|
-
if (local !== undefined) return await
|
|
71
|
+
if (local !== undefined) return await epic_add.add_children(to_add_input(local))
|
|
71
72
|
|
|
72
73
|
console.error(epic_cli.format_cross_repo_refusal(found))
|
|
73
74
|
|
|
@@ -109,7 +110,7 @@ async function run_addition(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
109
110
|
|
|
110
111
|
if (parsed === undefined) return await refuse_addition(argv)
|
|
111
112
|
|
|
112
|
-
return await
|
|
113
|
+
return await epic_add.add_children(to_add_input(parsed))
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
// Deleting a declared order. No position and no `--ordered`: the path itself says which orders go,
|
|
@@ -126,7 +127,7 @@ async function run_removal(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
126
127
|
return FAILURE_EXIT_CODE
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
return await
|
|
130
|
+
return await epic_remove.remove_order({
|
|
130
131
|
epic_number: parsed.epic_number,
|
|
131
132
|
path: parsed.path,
|
|
132
133
|
decision: epic_cli.read_decision(parsed.decision_path),
|
|
@@ -144,7 +145,7 @@ async function run_reconciliation(argv: ReadonlyArray<string>): Promise<number>
|
|
|
144
145
|
return FAILURE_EXIT_CODE
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
return await
|
|
148
|
+
return await epic_reconcile.reconcile_epic(parsed.epic_number)
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
async function run_creation(argv: ReadonlyArray<string>): Promise<number> {
|
|
@@ -156,7 +157,7 @@ async function run_creation(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
156
157
|
return FAILURE_EXIT_CODE
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
return await
|
|
160
|
+
return await epic_run.create_epic({
|
|
160
161
|
title: parsed.title,
|
|
161
162
|
children: parsed.children,
|
|
162
163
|
rationale: epic_cli.read_rationale(parsed.rationale_path),
|
|
@@ -187,6 +188,6 @@ async function main(): Promise<void> {
|
|
|
187
188
|
try {
|
|
188
189
|
await main()
|
|
189
190
|
} catch (error) {
|
|
190
|
-
console.error(`✖ ${
|
|
191
|
+
console.error(`✖ ${error_text.message_of(error)}`)
|
|
191
192
|
process.exit(FAILURE_EXIT_CODE)
|
|
192
193
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
4
5
|
import { z } from 'zod'
|
|
5
6
|
import { fix_gh_packages_logic, type LockfilePackage } from './fix-gh-packages-logic'
|
|
6
7
|
import { gh_cli_token } from './gh-cli-token'
|
|
@@ -19,7 +20,7 @@ const npm_packument_schema = z.looseObject({
|
|
|
19
20
|
type ParsedPackages = Record<string, LockfilePackage>
|
|
20
21
|
|
|
21
22
|
function read_file(file_path: string): string {
|
|
22
|
-
return
|
|
23
|
+
return file_reader.read_file_or_empty(file_path)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
// Resolution order: NODE_AUTH_TOKEN → a literal token in the project .npmrc → `gh auth token`.
|
package/scripts/gh/gh-spawn.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { git_gh_api_path } from '#scripts/git/git-gh-api-path'
|
|
2
2
|
import { git_gh_exec } from '#scripts/git/git-gh-exec'
|
|
3
3
|
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
4
|
-
import { execaSync } from 'execa'
|
|
5
4
|
|
|
6
5
|
// The synchronous twin of `git_gh_repo.repo_get_name_with_owner`: same fact, read the same way, but
|
|
7
|
-
//
|
|
6
|
+
// synchronously because `josh init` / `josh sync` / `josh doctor` decide before they can await.
|
|
8
7
|
// It asks REST for the same reason — `gh repo view` goes through GraphQL, which a cloud session is
|
|
9
8
|
// refused (403), and a repository that reads as unresolved makes `init` / `sync` skip
|
|
10
9
|
// `sonar-project.properties` outright (joshuafolkken/kit#1023). The path comes from the shared
|
|
11
10
|
// builder rather than being spelled out again here.
|
|
12
11
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// An unbounded lookup asks for `NO_BUDGET`, execa's own "no timeout": the shared layer otherwise
|
|
13
|
+
// applies its default budget, which would change what `init` / `sync` wait for.
|
|
14
|
+
const NO_BUDGET = 0
|
|
15
|
+
|
|
15
16
|
function fetch_repo_name(timeout_ms: number | undefined): string | undefined {
|
|
16
|
-
const
|
|
17
|
-
|
|
17
|
+
const stdout = git_gh_exec.read_gh_api_sync({
|
|
18
|
+
path: git_gh_api_path.repo_api_path(),
|
|
19
|
+
jq_filter: '.full_name',
|
|
18
20
|
cwd: PROJECT_ROOT,
|
|
19
|
-
|
|
20
|
-
...(timeout_ms !== undefined && { timeout: timeout_ms }),
|
|
21
|
+
timeout_ms: timeout_ms ?? NO_BUDGET,
|
|
21
22
|
})
|
|
22
|
-
if (result.exitCode !== 0 || !result.stdout) return undefined
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
const name = stdout?.trim()
|
|
25
|
+
|
|
26
|
+
return name === '' ? undefined : name
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
// The unbounded lookup, for callers whose *writes* depend on the answer. A timeout would surface as
|
package/scripts/git/constants.ts
CHANGED
|
@@ -10,7 +10,7 @@ const GIT_COMMAND_UNIX = '/usr/bin/git'
|
|
|
10
10
|
// modules (joshuafolkken/kit#1640) and a copy in each is a second answer to one question.
|
|
11
11
|
const PORCELAIN_FLAG = '--porcelain'
|
|
12
12
|
// The untracked-files mode every porcelain status reading passes (`git-command.ts` carries why it is
|
|
13
|
-
// passed and why it is `all`), shared so
|
|
13
|
+
// passed and why it is `all`), shared so another module's own reading cannot drift from it.
|
|
14
14
|
const UNTRACKED_FILES_FLAG = '--untracked-files=all'
|
|
15
15
|
|
|
16
16
|
function get_git_command(): string {
|
|
@@ -75,7 +75,7 @@ async function pull_latest(): Promise<void> {
|
|
|
75
75
|
)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const ISSUE_PREFIX_PATTERN =
|
|
78
|
+
const ISSUE_PREFIX_PATTERN = /^(\d+)-/u
|
|
79
79
|
|
|
80
80
|
function has_same_issue_prefix(branch_a: string, branch_b: string): boolean {
|
|
81
81
|
const prefix_a = ISSUE_PREFIX_PATTERN.exec(branch_a)?.[0]
|
|
@@ -84,6 +84,14 @@ function has_same_issue_prefix(branch_a: string, branch_b: string): boolean {
|
|
|
84
84
|
return prefix_a !== undefined && prefix_a === prefix_b
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// The issue a branch names, or `undefined` when it does not lead with one — `1937-lane` and
|
|
88
|
+
// `1937-add-a-scope` both read 1937, while `main` reads nothing.
|
|
89
|
+
function issue_from_branch(branch: string): number | undefined {
|
|
90
|
+
const digits = ISSUE_PREFIX_PATTERN.exec(branch)?.[1]
|
|
91
|
+
|
|
92
|
+
return digits === undefined ? undefined : Number(digits)
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
// The one rule `check_and_create_branch` refuses on, kept pure so `git-preflight.ts` asks it before
|
|
88
96
|
// anything is pulled, switched or staged.
|
|
89
97
|
function is_mismatch(
|
|
@@ -133,6 +141,7 @@ const git_branch = {
|
|
|
133
141
|
// second copy of the pattern (joshuafolkken/kit#1497). A copy is what let `lane/<N>` ship: nothing
|
|
134
142
|
// asserted that the name `lane:open` writes is one this function accepts.
|
|
135
143
|
has_same_issue_prefix,
|
|
144
|
+
issue_from_branch,
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
export { git_branch }
|
|
@@ -411,14 +411,14 @@ async function push(): Promise<void> {
|
|
|
411
411
|
// the same thing: it is on the default branch, bringing it up to date before doing something else —
|
|
412
412
|
// `main-sync.ts` for `josh ms`, `release-cli.ts` / `release-publish.ts` around the release pull
|
|
413
413
|
// request, `scripts-ai/prep.ts` before it snapshots the overrides, and `git-branch.ts` →
|
|
414
|
-
// `pull_latest` — reached from `
|
|
414
|
+
// `pull_latest` — reached from `git-workflow.ts`, so it runs on every `josh git` /
|
|
415
415
|
// `josh pr` started from the default branch, which makes it the hottest of the five rather than a
|
|
416
416
|
// dormant one. **None of them is
|
|
417
417
|
// asking to absorb divergence**, so `merge_branch`'s reasoning inverts here: a default branch that
|
|
418
418
|
// has diverged is a state to fail loudly on rather than to grow a merge commit over. The name says
|
|
419
419
|
// which of the two this is, as `merge_fast_forward` does beside `merge_branch`.
|
|
420
420
|
async function pull_fast_forward(): Promise<void> {
|
|
421
|
-
await git_spawn.
|
|
421
|
+
await git_spawn.with_output_remote('pull', ['--ff-only'])
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
// Every local branch matching a `git branch --list` pattern, one name per line. The boolean below is
|
package/scripts/git/git-error.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
1
2
|
import { has_stderr_field } from './git-gh-exec'
|
|
2
3
|
|
|
3
|
-
function get_error_message(error: unknown): string {
|
|
4
|
-
return error instanceof Error ? error.message : String(error)
|
|
5
|
-
}
|
|
6
|
-
|
|
7
4
|
function get_stderr_from_error(cause: Error): string | undefined {
|
|
8
5
|
if (!has_stderr_field(cause)) return undefined
|
|
9
6
|
|
|
@@ -46,7 +43,7 @@ function display_error_details(cause: unknown): void {
|
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
function handle(error: unknown): void {
|
|
49
|
-
const error_message =
|
|
46
|
+
const error_message = error_text.message_of(error)
|
|
50
47
|
|
|
51
48
|
console.error('')
|
|
52
49
|
console.error('❌ Error:', error_message)
|