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