@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
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type RepoAnswer,
|
|
7
7
|
} from './epic-candidate-confirm'
|
|
8
8
|
import { epic_graph, type EpicChild } from './epic-graph'
|
|
9
|
+
import { epic_rank } from './epic-rank'
|
|
9
10
|
import type { EpicVerdict } from './epic-report'
|
|
10
11
|
import { epic_solo } from './epic-solo'
|
|
11
12
|
import { epic_triage, type TriageVerdict } from './epic-triage'
|
|
@@ -107,6 +108,15 @@ function candidates_in(pool: RepoPool, repo: string): RepoPool {
|
|
|
107
108
|
return { ...pool, candidates: pool.candidates.filter((child) => child.repo === repo) }
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
// A pool's candidates for the repository in `epic_rank.rank` order — the ranking `backlog:next` offers
|
|
112
|
+
// in (joshuafolkken/kit#2928), counted against the pool's own graph. **Within a pool, not across
|
|
113
|
+
// pools**: the pool order is the order a person named the epics in, and that stays the outer key.
|
|
114
|
+
function ranked_in(pool: RepoPool, repo: string): RepoPool {
|
|
115
|
+
const scoped = candidates_in(pool, repo)
|
|
116
|
+
|
|
117
|
+
return { ...scoped, candidates: epic_rank.rank(scoped.candidates, pool.context.children) }
|
|
118
|
+
}
|
|
119
|
+
|
|
110
120
|
// One pool's candidates, minus every one an earlier pool already carries. `seen` is mutated rather
|
|
111
121
|
// than rebuilt because the earlier pools' claim is what the later one is filtered against.
|
|
112
122
|
function unseen_candidates(pool: RepoPool, seen: Set<string>): RepoPool {
|
|
@@ -184,24 +194,6 @@ function solo_offer(
|
|
|
184
194
|
return { ...answer, children: offered, notice: offered_notice(offered, read, request) }
|
|
185
195
|
}
|
|
186
196
|
|
|
187
|
-
// The confirmation walk stops at the free-lane count, so a `run:solo` candidate ranked past it — or
|
|
188
|
-
// in a pool past it — would never reach `solo_offer` to be preferred. Into an idle repository the
|
|
189
|
-
// first pool holding one is asked first, with it at its head through the same `epic_solo.prefer` the
|
|
190
|
-
// offer applies (joshuafolkken/kit#2778). The candidate stays in its own pool, because a pool carries
|
|
191
|
-
// the graph it is confirmed against.
|
|
192
|
-
function solo_first(pools: ReadonlyArray<RepoPool>, read: BusyRead): ReadonlyArray<RepoPool> {
|
|
193
|
-
if (read.kind !== 'idle') return pools
|
|
194
|
-
|
|
195
|
-
const index = pools.findIndex((pool) => pool.candidates.some((child) => epic_solo.is_solo(child)))
|
|
196
|
-
const pool = pools[index]
|
|
197
|
-
|
|
198
|
-
if (pool === undefined) return pools
|
|
199
|
-
|
|
200
|
-
const lifted = { ...pool, candidates: epic_solo.prefer(pool.candidates, read) }
|
|
201
|
-
|
|
202
|
-
return [lifted, ...pools.filter((other) => other !== pool)]
|
|
203
|
-
}
|
|
204
|
-
|
|
205
197
|
// The repository is asked how full it is **before** any candidate is confirmed, for the reason
|
|
206
198
|
// joshuafolkken/kit#1121 records: a repository with no free lane is handed nothing, so the relations
|
|
207
199
|
// request that would confirm a candidate there buys an answer nobody reads — and a polling `epicrun`
|
|
@@ -247,10 +239,7 @@ async function offer_for_repo(
|
|
|
247
239
|
}
|
|
248
240
|
}
|
|
249
241
|
|
|
250
|
-
const for_repo =
|
|
251
|
-
dedupe_pools(pools.map((pool) => candidates_in(pool, request.repo))),
|
|
252
|
-
read,
|
|
253
|
-
)
|
|
242
|
+
const for_repo = dedupe_pools(pools.map((pool) => ranked_in(pool, request.repo)))
|
|
254
243
|
|
|
255
244
|
return solo_offer(await collect(for_repo, wanted_of(request, free)), read, request)
|
|
256
245
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { repo_discovery } from '#scripts/discovery/repo-discovery'
|
|
4
|
-
import { git_epic_parse } from '#scripts/git/git-epic-parse'
|
|
5
4
|
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
6
5
|
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
7
6
|
import { josh_environment_file } from '#scripts/josh/josh-environment-file'
|
|
@@ -16,6 +15,7 @@ import { epic_lane_offer, type LaneOffer, type LaneRequest } from './epic-lane-o
|
|
|
16
15
|
import { epic_next_read, type EpicRead, type SnapshotReads } from './epic-next-read'
|
|
17
16
|
import { epic_next_views, type EpicView } from './epic-next-views'
|
|
18
17
|
import { epic_outside_blocker } from './epic-outside-blocker'
|
|
18
|
+
import { epic_parse } from './epic-parse'
|
|
19
19
|
import { epic_report, type EpicNextResult } from './epic-report'
|
|
20
20
|
|
|
21
21
|
// `josh epic:next <E…>` — which of the named epics' children can be started right now, bundled per
|
|
@@ -170,7 +170,7 @@ function unreadable_anomaly(snapshot: EpicSnapshot): GraphAnomaly | undefined {
|
|
|
170
170
|
// body whose declared chain and recorded relations agree — a worse outcome than following the chain
|
|
171
171
|
// the run's own `epic:check` already flagged.
|
|
172
172
|
function is_order_declared(body: string | undefined, links: ReadonlyArray<unknown>): boolean {
|
|
173
|
-
return links.length > 0 ||
|
|
173
|
+
return links.length > 0 || epic_parse.has_unordered_declaration(body)
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
function decide(
|
|
@@ -178,7 +178,7 @@ function decide(
|
|
|
178
178
|
paths: ReadonlyMap<string, string> = new Map(),
|
|
179
179
|
running?: ReadonlySet<string>,
|
|
180
180
|
): EpicNextResult {
|
|
181
|
-
const links =
|
|
181
|
+
const links = epic_parse.parse_dependency_links(snapshot.body)
|
|
182
182
|
const unreadable = body_anomaly(snapshot) ?? unreadable_anomaly(snapshot)
|
|
183
183
|
const anomalies =
|
|
184
184
|
unreadable === undefined
|
|
@@ -256,7 +256,7 @@ function parse_dependency_links(body: string | undefined): Array<DependencyLink>
|
|
|
256
256
|
// `None — the children are independent` report as ordered, and `josh followup` then said the batch
|
|
257
257
|
// order was never recorded on every child's merge. The narrowing joshuafolkken/kit#858 applied to
|
|
258
258
|
// the link reader was the same judgement, and `epic --add` writes on that judgement too
|
|
259
|
-
// (`
|
|
259
|
+
// (`epic-add-body.ts` protects an arrow outside the `Dependencies` section as prose) — so the
|
|
260
260
|
// three readers of one body now recognize the same lines, instead of two patterns that could
|
|
261
261
|
// disagree about which line is a declaration. They still answer different questions of those lines:
|
|
262
262
|
// a self-loop (`#101 -> #101`) is a declaration here and yields no link, since `chain_links` drops
|
|
@@ -306,7 +306,7 @@ function is_state_closed(state: string | undefined): boolean {
|
|
|
306
306
|
return state?.toUpperCase() === CLOSED_STATE
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
const
|
|
309
|
+
const epic_parse = {
|
|
310
310
|
fence_mask,
|
|
311
311
|
is_task_list_line,
|
|
312
312
|
is_declaration_line,
|
|
@@ -328,7 +328,7 @@ const git_epic_parse = {
|
|
|
328
328
|
|
|
329
329
|
export type { DeclarationState, DependencyLink, ExternalChild }
|
|
330
330
|
export {
|
|
331
|
-
|
|
331
|
+
epic_parse,
|
|
332
332
|
fence_mask,
|
|
333
333
|
is_task_list_line,
|
|
334
334
|
is_declaration_line,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { epic_body } from './epic-body'
|
|
2
|
+
import { epic_parse } from './epic-parse'
|
|
3
3
|
|
|
4
4
|
// Promoting an existing issue into an epic.
|
|
5
5
|
//
|
|
@@ -34,13 +34,13 @@ function has_conflicting_tracking(body: string | undefined): boolean {
|
|
|
34
34
|
if (body === undefined) return false
|
|
35
35
|
if (body.includes(PROMOTED_HEADING)) return true
|
|
36
36
|
|
|
37
|
-
return
|
|
37
|
+
return epic_parse.parse_task_list_issue_numbers(body).length > 0
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// Why the promotion was refused, phrased as what was found rather than as a rule number.
|
|
41
41
|
function conflict_reason(body: string | undefined): string {
|
|
42
42
|
if (body?.includes(PROMOTED_HEADING) === true) return 'it already carries the epic sections'
|
|
43
|
-
const tracked =
|
|
43
|
+
const tracked = epic_parse.parse_task_list_issue_numbers(body)
|
|
44
44
|
const list = tracked.map((child) => `#${String(child)}`).join(', ')
|
|
45
45
|
|
|
46
46
|
return `its body already tracks ${list} as a task list, which would become a second child list`
|
|
@@ -50,7 +50,7 @@ function conflict_reason(body: string | undefined): string {
|
|
|
50
50
|
// what happened. The original text is never rewritten — it is the record of why the split was made.
|
|
51
51
|
function build_promoted_body(input: PromoteInput): string {
|
|
52
52
|
const existing = (input.body ?? '').trimEnd()
|
|
53
|
-
const epic_sections =
|
|
53
|
+
const epic_sections = epic_body.build_epic_body({
|
|
54
54
|
children: input.children,
|
|
55
55
|
rationale: input.rationale,
|
|
56
56
|
is_ordered: input.is_ordered,
|
|
@@ -71,8 +71,8 @@ function build_promoted_body(input: PromoteInput): string {
|
|
|
71
71
|
// so the body would declare an order and declare that there is none. Reported as "does not track
|
|
72
72
|
// every child", that state left nothing to act on (joshuafolkken/kit#1155).
|
|
73
73
|
function declaration_error(body: string): string | undefined {
|
|
74
|
-
const state =
|
|
75
|
-
if (
|
|
74
|
+
const state = epic_parse.read_declaration(body)
|
|
75
|
+
if (epic_parse.is_declaration_readable(state)) return undefined
|
|
76
76
|
|
|
77
77
|
return state.has_chain
|
|
78
78
|
? 'The promoted body would declare an order and declare that there is none: the issue being promoted already carries a line that is only a chain (`#N -> #M`), and it contradicts the `Dependencies` section written for the epic. Reword that line first; nothing was written.'
|
|
@@ -80,7 +80,7 @@ function declaration_error(body: string): string | undefined {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
function find_tracking_error(body: string, children: ReadonlyArray<number>): string | undefined {
|
|
83
|
-
const tracked =
|
|
83
|
+
const tracked = epic_parse.parse_task_list_issue_numbers(body)
|
|
84
84
|
|
|
85
85
|
if (children.some((child) => !tracked.includes(child))) {
|
|
86
86
|
return 'The promoted body would not track every child; nothing was written.'
|
|
@@ -89,7 +89,7 @@ function find_tracking_error(body: string, children: ReadonlyArray<number>): str
|
|
|
89
89
|
return declaration_error(body)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
const
|
|
92
|
+
const epic_promote = {
|
|
93
93
|
EPIC_SECTION_MARKER,
|
|
94
94
|
PROMOTED_HEADING,
|
|
95
95
|
has_conflicting_tracking,
|
|
@@ -99,4 +99,4 @@ const git_epic_promote = {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export type { PromoteInput }
|
|
102
|
-
export {
|
|
102
|
+
export { epic_promote }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { issue_rank } from '#scripts/git/issue-rank'
|
|
2
|
+
import { epic_graph, type EpicChild } from './epic-graph'
|
|
3
|
+
|
|
4
|
+
// `issue-rank.ts`'s keys over graph children (joshuafolkken/kit#2928), for both offer paths that hand
|
|
5
|
+
// out children: `backlog:next` (`backlog-rank.ts`) and a named epic's `epic:next --lanes`
|
|
6
|
+
// (`epic-lane-offer.ts`). One ranking, so the two cannot disagree about which child goes first.
|
|
7
|
+
|
|
8
|
+
const CLOSED = 'CLOSED'
|
|
9
|
+
|
|
10
|
+
// How many open issues of the pool wait on each child. Only the pool is counted: it is every issue the
|
|
11
|
+
// caller may run, and an issue outside it is not one this ranking can start sooner.
|
|
12
|
+
function dependents_of(pool: ReadonlyArray<EpicChild>): ReadonlyMap<string, number> {
|
|
13
|
+
return issue_rank.count_dependents(
|
|
14
|
+
pool
|
|
15
|
+
.filter((child) => child.state !== CLOSED)
|
|
16
|
+
.map((child) => ({
|
|
17
|
+
key: epic_graph.key_of(child),
|
|
18
|
+
blockers: child.blocked_by.map((blocker) => epic_graph.blocker_key(blocker)),
|
|
19
|
+
})),
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function rank(
|
|
24
|
+
children: ReadonlyArray<EpicChild>,
|
|
25
|
+
pool: ReadonlyArray<EpicChild>,
|
|
26
|
+
): Array<EpicChild> {
|
|
27
|
+
const dependents = dependents_of(pool)
|
|
28
|
+
|
|
29
|
+
return issue_rank.rank(children, (child) => ({
|
|
30
|
+
labels: child.labels,
|
|
31
|
+
dependents: dependents.get(epic_graph.key_of(child)) ?? 0,
|
|
32
|
+
}))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const epic_rank = { dependents_of, rank }
|
|
36
|
+
|
|
37
|
+
export { epic_rank }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
2
|
+
import { epic_fetch } from './epic-fetch'
|
|
3
|
+
import { epic_graph, type EpicChild } from './epic-graph'
|
|
4
|
+
import { epic_parse } from './epic-parse'
|
|
5
|
+
import { epic_validate, type EpicSubject } from './epic-validate'
|
|
6
6
|
|
|
7
7
|
// Reading an epic and its current graph, for the two commands that edit its declared order.
|
|
8
8
|
//
|
|
@@ -26,7 +26,7 @@ interface EpicReading {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
async function read_subject(epic_number: number): Promise<EpicSubject | undefined> {
|
|
29
|
-
return
|
|
29
|
+
return epic_validate.parse_epic_subject(
|
|
30
30
|
await git_gh_command.issue_get_labels_and_body(String(epic_number)),
|
|
31
31
|
)
|
|
32
32
|
}
|
|
@@ -39,7 +39,7 @@ async function read_recorded(
|
|
|
39
39
|
): Promise<{ children: ReadonlyArray<EpicChild>; repo: string } | { error: string }> {
|
|
40
40
|
const repo = await git_gh_command.repo_get_name_with_owner()
|
|
41
41
|
if (repo === undefined) return { error: UNKNOWN_REPO }
|
|
42
|
-
const tracked =
|
|
42
|
+
const tracked = epic_parse.parse_task_list_issue_numbers(body)
|
|
43
43
|
if (tracked.length === 0) return { children: [], repo }
|
|
44
44
|
|
|
45
45
|
const fetched = await epic_fetch.fetch_children(tracked, repo)
|
|
@@ -64,10 +64,10 @@ async function read_epic(epic_number: number): Promise<EpicReading | { error: st
|
|
|
64
64
|
return { subject, recorded: recorded.children, repo: recorded.repo }
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const
|
|
67
|
+
const epic_read = {
|
|
68
68
|
UNKNOWN_REPO,
|
|
69
69
|
read_epic,
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export {
|
|
72
|
+
export { epic_read }
|
|
73
73
|
export type { EpicReading }
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
2
|
+
import { epic_add_body } from './epic-add-body'
|
|
3
|
+
import { epic_chains } from './epic-chains'
|
|
4
|
+
import { epic_graph, type EpicChild, type IssueReference } from './epic-graph'
|
|
5
|
+
import { epic_parse, type DependencyLink } from './epic-parse'
|
|
6
|
+
import { epic_read } from './epic-read'
|
|
7
|
+
import { format_dependency_links } from './epic-reference'
|
|
8
|
+
import { epic_relations } from './epic-relations'
|
|
9
9
|
|
|
10
10
|
// `josh epic --reconcile <E>` — bring an epic's `## Dependencies` declaration and its native
|
|
11
11
|
// `blocked-by` relations back into agreement, without a hand edit and without recording a decision
|
|
@@ -34,7 +34,7 @@ const RECONCILED = 'reconciled'
|
|
|
34
34
|
|
|
35
35
|
interface ReconcilePlanInput {
|
|
36
36
|
body: string | undefined
|
|
37
|
-
// The epic's children with their native relations, exactly as `
|
|
37
|
+
// The epic's children with their native relations, exactly as `epic_read` reads them.
|
|
38
38
|
recorded: ReadonlyArray<EpicChild>
|
|
39
39
|
// The epic's own repository — what a declared bare number names (joshuafolkken/kit#1126).
|
|
40
40
|
repo: string
|
|
@@ -101,7 +101,7 @@ function to_reconciled_body(
|
|
|
101
101
|
body: string | undefined,
|
|
102
102
|
target_chains: ReadonlyArray<ReadonlyArray<number>>,
|
|
103
103
|
): { body: string } | { error: string } {
|
|
104
|
-
return
|
|
104
|
+
return epic_add_body.rewrite_body({
|
|
105
105
|
body: body ?? '',
|
|
106
106
|
placed: [],
|
|
107
107
|
position: undefined,
|
|
@@ -120,8 +120,8 @@ interface Mismatch {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
function read_mismatch(input: ReconcilePlanInput): Mismatch {
|
|
123
|
-
const chains_before =
|
|
124
|
-
const declared =
|
|
123
|
+
const chains_before = epic_parse.parse_dependency_chains(input.body)
|
|
124
|
+
const declared = epic_chains.links_of(chains_before)
|
|
125
125
|
|
|
126
126
|
return {
|
|
127
127
|
chains_before,
|
|
@@ -135,7 +135,7 @@ function read_mismatch(input: ReconcilePlanInput): Mismatch {
|
|
|
135
135
|
function to_reconciled_plan(input: ReconcilePlanInput, mismatch: Mismatch): ReconcilePlan {
|
|
136
136
|
const declared_chains = mismatch.declare.map((link) => to_chain(link))
|
|
137
137
|
const target_chains = [...mismatch.chains_before, ...declared_chains]
|
|
138
|
-
const cycle = find_cycle(
|
|
138
|
+
const cycle = find_cycle(epic_chains.links_of(target_chains), input.repo)
|
|
139
139
|
if (cycle !== undefined) return { kind: 'cycle', message: cycle }
|
|
140
140
|
|
|
141
141
|
const { declare, record } = mismatch
|
|
@@ -176,11 +176,9 @@ function report_declared(epic_number: number, declare: ReadonlyArray<DependencyL
|
|
|
176
176
|
async function record_relations(record: ReadonlyArray<DependencyLink>): Promise<void> {
|
|
177
177
|
if (record.length === 0) return
|
|
178
178
|
|
|
179
|
-
const failures = await
|
|
179
|
+
const failures = await epic_relations.apply_relations(record, 'record')
|
|
180
180
|
|
|
181
|
-
console.info(
|
|
182
|
-
git_epic_relations.format_relation_report({ links: record, failures, action: 'record' }),
|
|
183
|
-
)
|
|
181
|
+
console.info(epic_relations.format_relation_report({ links: record, failures, action: 'record' }))
|
|
184
182
|
}
|
|
185
183
|
|
|
186
184
|
async function apply_reconciliation(
|
|
@@ -218,7 +216,7 @@ async function apply_plan(epic_number: number, plan: ReconcilePlan): Promise<num
|
|
|
218
216
|
// Every refusal — an unreadable epic, an unreadable graph, a cycle — happens before the body edit,
|
|
219
217
|
// so a rejected invocation leaves the epic exactly as it was.
|
|
220
218
|
async function reconcile_epic(epic_number: number): Promise<number> {
|
|
221
|
-
const epic = await
|
|
219
|
+
const epic = await epic_read.read_epic(epic_number)
|
|
222
220
|
|
|
223
221
|
if ('error' in epic) {
|
|
224
222
|
console.error(`✖ ${epic.error}`)
|
|
@@ -235,12 +233,12 @@ async function reconcile_epic(epic_number: number): Promise<number> {
|
|
|
235
233
|
return await apply_plan(epic_number, plan)
|
|
236
234
|
}
|
|
237
235
|
|
|
238
|
-
const
|
|
236
|
+
const epic_reconcile = {
|
|
239
237
|
NOTHING_TO_RECONCILE,
|
|
240
238
|
RECONCILED,
|
|
241
239
|
build_reconcile_plan,
|
|
242
240
|
reconcile_epic,
|
|
243
241
|
}
|
|
244
242
|
|
|
245
|
-
export {
|
|
243
|
+
export { epic_reconcile }
|
|
246
244
|
export type { ReconcilePlan, ReconcilePlanInput, Reconciliation }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DependencyLink } from './
|
|
1
|
+
import type { DependencyLink } from './epic-parse'
|
|
2
2
|
|
|
3
3
|
// How an issue and a dependency between two of them are written down.
|
|
4
4
|
//
|
|
@@ -13,25 +13,6 @@ const DEPENDENCY_ARROW = ' -> '
|
|
|
13
13
|
// and two separators would let the two disagree over something a reader reads side by side.
|
|
14
14
|
const REFERENCE_SEPARATOR = ', '
|
|
15
15
|
|
|
16
|
-
// An issue named from outside any one graph — a task-list row, a citation in prose, a blocker
|
|
17
|
-
// relation, a child that could not be read. Repository **and** number, because a number alone cannot
|
|
18
|
-
// identify one: issue numbers are unique per repository, so the same `#40` names two different issues
|
|
19
|
-
// depending on who wrote it (joshuafolkken/kit#1014).
|
|
20
|
-
//
|
|
21
|
-
// It lives here rather than in `epic-graph` because the `blocked-by` relations are read in the git
|
|
22
|
-
// layer and carry a repository of their own (joshuafolkken/kit#1126) — a second declaration there
|
|
23
|
-
// would be the clone `CLAUDE.md` prohibits, and an import the other way would point the lower layer
|
|
24
|
-
// at the higher one. `epic-graph` re-exports it, so every existing importer is unchanged.
|
|
25
|
-
//
|
|
26
|
-
// `state` is present only on a reference read from a `blocked-by` relation whose response carried it
|
|
27
|
-
// (joshuafolkken/kit#1943). The classifier needs it for a blocker no named graph tracks, where there is
|
|
28
|
-
// no child record to read the state from; every other reference leaves it out.
|
|
29
|
-
interface IssueReference {
|
|
30
|
-
repo: string
|
|
31
|
-
number: number
|
|
32
|
-
state?: 'OPEN' | 'CLOSED'
|
|
33
|
-
}
|
|
34
|
-
|
|
35
16
|
function to_issue_reference(issue_number: number): string {
|
|
36
17
|
return `#${String(issue_number)}`
|
|
37
18
|
}
|
|
@@ -73,7 +54,7 @@ function format_replaced_relations(links: ReadonlyArray<DependencyLink>): string
|
|
|
73
54
|
return `Replaced blocked-by: ${join_references(quoted)}.`
|
|
74
55
|
}
|
|
75
56
|
|
|
76
|
-
const
|
|
57
|
+
const epic_reference = {
|
|
77
58
|
DEPENDENCY_ARROW,
|
|
78
59
|
REFERENCE_SEPARATOR,
|
|
79
60
|
to_issue_reference,
|
|
@@ -84,9 +65,9 @@ const git_epic_reference = {
|
|
|
84
65
|
format_replaced_relations,
|
|
85
66
|
}
|
|
86
67
|
|
|
87
|
-
export type { IssueReference }
|
|
68
|
+
export type { IssueReference } from '#scripts/git/issue-reference'
|
|
88
69
|
export {
|
|
89
|
-
|
|
70
|
+
epic_reference,
|
|
90
71
|
DEPENDENCY_ARROW,
|
|
91
72
|
REFERENCE_SEPARATOR,
|
|
92
73
|
to_issue_reference,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
2
2
|
import { epic_graph, type EpicChild, type IssueReference } from './epic-graph'
|
|
3
|
+
import { epic_parse } from './epic-parse'
|
|
3
4
|
|
|
4
5
|
// joshuafolkken/kit#1113: a second look at the relations, taken only where the first one is about to
|
|
5
6
|
// become a verdict.
|
|
@@ -42,7 +43,7 @@ function suspect_children(
|
|
|
42
43
|
body: string | undefined,
|
|
43
44
|
declared_repo: string,
|
|
44
45
|
): Set<number> {
|
|
45
|
-
const links =
|
|
46
|
+
const links = epic_parse.parse_dependency_links(body)
|
|
46
47
|
const local = children.filter((child) => child.repo === declared_repo)
|
|
47
48
|
|
|
48
49
|
return new Set(
|
|
@@ -57,7 +58,7 @@ async function reread_child(child: EpicChild, read_blockers: BlockersReader): Pr
|
|
|
57
58
|
try {
|
|
58
59
|
return { ...child, blocked_by: await read_blockers(child) }
|
|
59
60
|
} catch (error) {
|
|
60
|
-
const reason =
|
|
61
|
+
const reason = error_text.message_of(error)
|
|
61
62
|
|
|
62
63
|
console.warn(
|
|
63
64
|
`⚠ could not re-read the blockers of #${String(child.number)}: ${reason}\n` +
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
2
|
+
import type { DependencyLink } from './epic-parse'
|
|
3
|
+
import { format_dependency_links } from './epic-reference'
|
|
4
4
|
|
|
5
5
|
// Recording and dropping the native `blocked-by` relations an epic's declared order implies.
|
|
6
6
|
//
|
|
@@ -69,10 +69,10 @@ function format_relation_report(input: {
|
|
|
69
69
|
return `⚠️ ${String(input.failures)} of ${total} blocked-by relation(s) could not be ${verb}; the epic body is intact.`
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
const
|
|
72
|
+
const epic_relations = {
|
|
73
73
|
apply_relations,
|
|
74
74
|
format_relation_report,
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export {
|
|
77
|
+
export { epic_relations }
|
|
78
78
|
export type { RelationAction }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { chain_links,
|
|
6
|
-
import { format_dependency_link, format_dependency_links } from './
|
|
7
|
-
import {
|
|
1
|
+
import { epic_add_body, type RewriteOutcome } from './epic-add-body'
|
|
2
|
+
import { epic_chains } from './epic-chains'
|
|
3
|
+
import { epic_decision } from './epic-decision'
|
|
4
|
+
import { epic_graph, type EpicChild } from './epic-graph'
|
|
5
|
+
import { chain_links, epic_parse, type DependencyLink } from './epic-parse'
|
|
6
|
+
import { format_dependency_link, format_dependency_links } from './epic-reference'
|
|
7
|
+
import { epic_shape } from './epic-shape'
|
|
8
8
|
|
|
9
9
|
// Everything `josh epic --remove` decides before it writes anything.
|
|
10
10
|
//
|
|
@@ -15,7 +15,7 @@ import { git_epic_shape } from './git-epic-shape'
|
|
|
15
15
|
// `declaration_mismatch`, which stops an unattended run. Epic joshuafolkken/kit#1262 recorded
|
|
16
16
|
// twenty-nine such removals before the command existed (joshuafolkken/kit#1712).
|
|
17
17
|
//
|
|
18
|
-
// Kept apart from the GitHub calls for the reason `
|
|
18
|
+
// Kept apart from the GitHub calls for the reason `epic-add-plan.ts` is: the whole decision is
|
|
19
19
|
// asserted without a network, and a refusal path only reachable through `gh` is one nobody tests.
|
|
20
20
|
|
|
21
21
|
// The path whose consecutive pairs name the orders to delete. `--remove <E> 101 102 103` deletes
|
|
@@ -71,9 +71,7 @@ function find_undeclared_error(
|
|
|
71
71
|
links: ReadonlyArray<DependencyLink>,
|
|
72
72
|
chains: ReadonlyArray<ReadonlyArray<number>>,
|
|
73
73
|
): string | undefined {
|
|
74
|
-
const declared = new Set(
|
|
75
|
-
git_epic_chains.links_of(chains).map((link) => format_dependency_link(link)),
|
|
76
|
-
)
|
|
74
|
+
const declared = new Set(epic_chains.links_of(chains).map((link) => format_dependency_link(link)))
|
|
77
75
|
const missing = links.filter((link) => !declared.has(format_dependency_link(link)))
|
|
78
76
|
|
|
79
77
|
return missing.length === 0
|
|
@@ -84,7 +82,7 @@ function find_undeclared_error(
|
|
|
84
82
|
// A decision record that cannot be written is refused here rather than dropped, exactly as it is for
|
|
85
83
|
// an insertion: `--decision-file` was given because the record has to exist.
|
|
86
84
|
function find_decision_error(decision: string | undefined): string | undefined {
|
|
87
|
-
return decision === undefined ? undefined :
|
|
85
|
+
return decision === undefined ? undefined : epic_decision.find_decision_error(decision)
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
function find_input_error(
|
|
@@ -93,7 +91,7 @@ function find_input_error(
|
|
|
93
91
|
chains: ReadonlyArray<ReadonlyArray<number>>,
|
|
94
92
|
): string | undefined {
|
|
95
93
|
return (
|
|
96
|
-
|
|
94
|
+
epic_shape.find_subject_error(input, tracked) ??
|
|
97
95
|
find_path_error(input.path) ??
|
|
98
96
|
find_undeclared_error(to_path_links(input.path), chains) ??
|
|
99
97
|
find_decision_error(input.decision)
|
|
@@ -112,7 +110,7 @@ function to_body(
|
|
|
112
110
|
input: RemovePlanInput,
|
|
113
111
|
chains_after: ReadonlyArray<ReadonlyArray<number>>,
|
|
114
112
|
): RewriteOutcome {
|
|
115
|
-
return
|
|
113
|
+
return epic_add_body.rewrite_body({
|
|
116
114
|
body: input.body ?? '',
|
|
117
115
|
placed: [],
|
|
118
116
|
chains_after,
|
|
@@ -122,13 +120,13 @@ function to_body(
|
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
function build_removal_plan(input: RemovePlanInput): RemoveOutcome {
|
|
125
|
-
const tracked =
|
|
126
|
-
const chains_before =
|
|
123
|
+
const tracked = epic_parse.parse_task_list_issue_numbers(input.body)
|
|
124
|
+
const chains_before = epic_parse.parse_dependency_chains(input.body)
|
|
127
125
|
const error = find_input_error(input, tracked, chains_before)
|
|
128
126
|
if (error !== undefined) return { error }
|
|
129
127
|
|
|
130
128
|
const links = to_path_links(input.path)
|
|
131
|
-
const chains_after =
|
|
129
|
+
const chains_after = epic_chains.remove_links(chains_before, links)
|
|
132
130
|
const rewritten = to_body(input, chains_after)
|
|
133
131
|
if ('error' in rewritten) return { error: rewritten.error }
|
|
134
132
|
|
|
@@ -144,9 +142,9 @@ function build_removal_plan(input: RemovePlanInput): RemoveOutcome {
|
|
|
144
142
|
}
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
const
|
|
145
|
+
const epic_remove_plan = {
|
|
148
146
|
build_removal_plan,
|
|
149
147
|
}
|
|
150
148
|
|
|
151
|
-
export {
|
|
149
|
+
export { epic_remove_plan }
|
|
152
150
|
export type { RemovePlan, RemovePlanInput, RemoveOutcome }
|
|
@@ -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_decision } from './epic-decision'
|
|
3
|
+
import { epic_read } from './epic-read'
|
|
4
|
+
import { format_dependency_links } from './epic-reference'
|
|
5
|
+
import { epic_relations } from './epic-relations'
|
|
6
|
+
import { epic_remove_plan, type RemovePlan } from './epic-remove-plan'
|
|
7
7
|
|
|
8
8
|
// `josh epic --remove <E> <M> <N> [<N2> ...] [--decision-file <path|->]` — delete a declared order
|
|
9
9
|
// from the epic's body and from the native `blocked-by` relations in one input
|
|
@@ -32,7 +32,7 @@ async function comment_decision(ends: ReadonlyArray<number>, decision: string):
|
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
console.info(
|
|
35
|
-
|
|
35
|
+
epic_decision.format_decision_report({
|
|
36
36
|
total: ends.length,
|
|
37
37
|
failures: posted.filter((is_posted) => !is_posted).length,
|
|
38
38
|
}),
|
|
@@ -43,7 +43,7 @@ function report_relations(plan: RemovePlan, failures: number): void {
|
|
|
43
43
|
if (plan.removed.length === 0) return
|
|
44
44
|
|
|
45
45
|
console.info(
|
|
46
|
-
|
|
46
|
+
epic_relations.format_relation_report({ links: plan.removed, failures, action: 'drop' }),
|
|
47
47
|
)
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -56,7 +56,7 @@ async function write_plan(
|
|
|
56
56
|
console.info(
|
|
57
57
|
`📋 Removed ${format_dependency_links(plan.links)} from epic #${String(epic_number)}.`,
|
|
58
58
|
)
|
|
59
|
-
report_relations(plan, await
|
|
59
|
+
report_relations(plan, await epic_relations.apply_relations(plan.removed, 'drop'))
|
|
60
60
|
|
|
61
61
|
if (decision !== undefined) await comment_decision(plan.ends, decision)
|
|
62
62
|
}
|
|
@@ -64,7 +64,7 @@ async function write_plan(
|
|
|
64
64
|
// Delete a declared order, or refuse without writing anything. Every refusal happens before the body
|
|
65
65
|
// edit, so a rejected invocation leaves the epic exactly as it was.
|
|
66
66
|
async function remove_order(input: RemoveOrderInput): Promise<number> {
|
|
67
|
-
const epic = await
|
|
67
|
+
const epic = await epic_read.read_epic(input.epic_number)
|
|
68
68
|
|
|
69
69
|
if ('error' in epic) {
|
|
70
70
|
console.error(`✖ ${epic.error}`)
|
|
@@ -72,7 +72,7 @@ async function remove_order(input: RemoveOrderInput): Promise<number> {
|
|
|
72
72
|
return FAILURE_EXIT_CODE
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const outcome =
|
|
75
|
+
const outcome = epic_remove_plan.build_removal_plan({
|
|
76
76
|
epic_number: input.epic_number,
|
|
77
77
|
body: epic.subject.body,
|
|
78
78
|
labels: epic.subject.labels,
|
|
@@ -93,9 +93,9 @@ async function remove_order(input: RemoveOrderInput): Promise<number> {
|
|
|
93
93
|
return SUCCESS_EXIT_CODE
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
const
|
|
96
|
+
const epic_remove = {
|
|
97
97
|
remove_order,
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export {
|
|
100
|
+
export { epic_remove }
|
|
101
101
|
export type { RemoveOrderInput }
|