@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,5 +1,5 @@
|
|
|
1
|
-
import { chain_links, type DependencyLink } from './
|
|
2
|
-
import { DEPENDENCY_ARROW, format_dependency_link, to_issue_reference } from './
|
|
1
|
+
import { chain_links, type DependencyLink } from './epic-parse'
|
|
2
|
+
import { DEPENDENCY_ARROW, format_dependency_link, to_issue_reference } from './epic-reference'
|
|
3
3
|
|
|
4
4
|
// The declared dependency order of an epic, as the shape an insertion needs.
|
|
5
5
|
//
|
|
@@ -392,7 +392,7 @@ function insert_children(
|
|
|
392
392
|
return duplicated === undefined ? outcome : { error: already_declared_error(duplicated) }
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
const
|
|
395
|
+
const epic_chains = {
|
|
396
396
|
render_chains,
|
|
397
397
|
links_of,
|
|
398
398
|
diff_links,
|
|
@@ -407,5 +407,5 @@ const git_epic_chains = {
|
|
|
407
407
|
insert_children,
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
-
export {
|
|
410
|
+
export { epic_chains }
|
|
411
411
|
export type { Chain, Chains, InsertKind, InsertOutcome, InsertPosition, LinkDiff }
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Check an existing epic Issue against the four requirements, reporting each as pass or fail.
|
|
4
4
|
*
|
|
5
|
-
* Usage: tsx scripts
|
|
5
|
+
* Usage: tsx scripts/epic/epic-check.ts <issue-number>
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
8
8
|
import { epic_cli } from './epic-cli'
|
|
9
|
+
import { epic_run } from './epic-run'
|
|
9
10
|
|
|
10
11
|
const ARGV_OFFSET = 2
|
|
11
12
|
const USAGE = 'Usage: josh epic:check <issue-number>'
|
|
@@ -19,7 +20,7 @@ async function main(): Promise<void> {
|
|
|
19
20
|
process.exit(FAILURE_EXIT_CODE)
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const exit_code = await
|
|
23
|
+
const exit_code = await epic_run.check_epic(epic_number)
|
|
23
24
|
|
|
24
25
|
if (exit_code !== 0) process.exit(exit_code)
|
|
25
26
|
}
|
|
@@ -29,6 +30,6 @@ async function main(): Promise<void> {
|
|
|
29
30
|
try {
|
|
30
31
|
await main()
|
|
31
32
|
} catch (error) {
|
|
32
|
-
console.error(`✖ ${
|
|
33
|
+
console.error(`✖ ${error_text.message_of(error)}`)
|
|
33
34
|
process.exit(FAILURE_EXIT_CODE)
|
|
34
35
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { cli_body } from '../scripts/josh/cli-body'
|
|
1
|
+
import { cli_body } from '#scripts/josh/cli-body'
|
|
2
|
+
import type { InsertKind, InsertPosition } from './epic-chains'
|
|
4
3
|
import { epic_cli_argv, ISSUE_NUMBER_PATTERN } from './epic-cli-argv'
|
|
5
4
|
import { epic_cli_remove } from './epic-cli-remove'
|
|
5
|
+
import { epic_parse, type ExternalChild } from './epic-parse'
|
|
6
6
|
|
|
7
7
|
// Parsing lives apart from the entry point so the argument rules can be asserted without spawning a
|
|
8
8
|
// process or reaching GitHub. The entry point is then a thin shell around these two functions.
|
|
@@ -290,7 +290,7 @@ interface CrossRepoAddTarget {
|
|
|
290
290
|
function find_cross_repo_add_target(argv: ReadonlyArray<string>): CrossRepoAddTarget | undefined {
|
|
291
291
|
const [raw_epic] = to_positional_arguments(argv, ADD_VALUE_FLAGS)
|
|
292
292
|
if (raw_epic === undefined) return undefined
|
|
293
|
-
const epic =
|
|
293
|
+
const epic = epic_parse.parse_external_reference(raw_epic)
|
|
294
294
|
if (epic === undefined) return undefined
|
|
295
295
|
|
|
296
296
|
const local = parse_add_arguments(
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
2
|
+
import { read_json_listing } from '#scripts/git/parse-json-array'
|
|
3
|
+
import { rest_comment_schema, type RestCommentData } from '#scripts/git/schemas'
|
|
4
|
+
import { epic_parse, type ExternalChild } from './epic-parse'
|
|
5
5
|
|
|
6
6
|
// The comment the epic auto-close announces itself with, and how a run recognizes the one a previous
|
|
7
7
|
// run already posted.
|
|
8
8
|
//
|
|
9
|
-
// Kept apart from `
|
|
9
|
+
// Kept apart from `epic-close.ts` because the two answer different questions — that file decides
|
|
10
10
|
// *whether* an epic may close, this one decides what the announcement says and whether it is already
|
|
11
11
|
// there — and because the file it was split from is within twenty lines of the three hundred a file
|
|
12
12
|
// may hold (joshuafolkken/kit#1039).
|
|
@@ -131,7 +131,7 @@ function is_legacy_announcement(body: string): boolean {
|
|
|
131
131
|
// both: a listing that could not be read says nothing about what is on the issue, and reporting it
|
|
132
132
|
// as "nothing is there" would have the run state a fact it never established — the misread
|
|
133
133
|
// joshuafolkken/kit#973 and joshuafolkken/kit#959 were each about. Keeping it apart is what lets
|
|
134
|
-
// `
|
|
134
|
+
// `epic-close.ts` say out loud that the duplicate check did not run, which is the whole
|
|
135
135
|
// difference between the two answers there.
|
|
136
136
|
//
|
|
137
137
|
// **Both of `read_json_listing`'s gaps arrive as `unreadable`**, rows the schema rejects included.
|
|
@@ -147,7 +147,7 @@ type CloseCommentState = 'present' | 'absent' | 'unreadable'
|
|
|
147
147
|
// verbatim — a fenced one caught by the mask, an indented one by the indent check. `fence_mask` is the same walk every other body predicate
|
|
148
148
|
// in this codebase reads through, rather than a second one written here (joshuafolkken/kit#890).
|
|
149
149
|
function has_marker(body: string, children: ReadonlyArray<string>): boolean {
|
|
150
|
-
const mask =
|
|
150
|
+
const mask = epic_parse.fence_mask(body)
|
|
151
151
|
|
|
152
152
|
return body.split('\n').some((line, index) => {
|
|
153
153
|
if (mask[index] !== true) return false
|
|
@@ -190,10 +190,10 @@ async function read_close_comment_state(
|
|
|
190
190
|
return has_announcement(listing.rows, child_references(epic)) ? 'present' : 'absent'
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
const
|
|
193
|
+
const epic_close_comment = {
|
|
194
194
|
build_close_comment,
|
|
195
195
|
read_close_comment_state,
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
export type { CloseCommentState, EpicChildren }
|
|
199
|
-
export {
|
|
199
|
+
export { epic_close_comment, CLOSE_ANNOUNCEMENT }
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
2
|
+
import { EPIC_LABEL } from '#scripts/git/issue-labels'
|
|
3
|
+
import { cutoff_cause, cutoff_of, type ScanCutoff } from '#scripts/git/listing-cutoff'
|
|
4
|
+
import {
|
|
5
|
+
parse_json_array_or_undefined,
|
|
6
|
+
parse_json_object_safe,
|
|
7
|
+
} from '#scripts/git/parse-json-array'
|
|
8
|
+
import { epic_child_schema, epic_issue_schema, type EpicChildData } from '#scripts/git/schemas'
|
|
9
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
10
|
+
import { epic_close_comment } from './epic-close-comment'
|
|
11
|
+
import { epic_parse, type ExternalChild } from './epic-parse'
|
|
8
12
|
|
|
9
13
|
// How many open epics the auto-close will look at. `git-gh-issue-list.ts` pages until `limit` rows
|
|
10
14
|
// have been selected or the backlog runs out, so this is what keeps the ordinary case at exactly one
|
|
@@ -44,9 +48,9 @@ interface SiblingState {
|
|
|
44
48
|
function to_epic_issue(raw: { number: number; body?: string | undefined }): EpicIssue {
|
|
45
49
|
return {
|
|
46
50
|
number: raw.number,
|
|
47
|
-
children:
|
|
48
|
-
external_children:
|
|
49
|
-
has_declared_order:
|
|
51
|
+
children: epic_parse.parse_task_list_issue_numbers(raw.body),
|
|
52
|
+
external_children: epic_parse.parse_external_task_list_children(raw.body),
|
|
53
|
+
has_declared_order: epic_parse.has_declared_dependency_chain(raw.body),
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -91,7 +95,7 @@ function to_sibling_state(raw_json: string | undefined): SiblingState {
|
|
|
91
95
|
const child = parse_child(raw_json)
|
|
92
96
|
|
|
93
97
|
return {
|
|
94
|
-
is_closed:
|
|
98
|
+
is_closed: epic_parse.is_state_closed(child?.state),
|
|
95
99
|
has_blocked_by: has_blocked_by(child),
|
|
96
100
|
}
|
|
97
101
|
}
|
|
@@ -124,7 +128,7 @@ async function inspect_siblings(
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
// `pnpm josh epic --ordered` and `--add` record the declared chain themselves, through the REST
|
|
127
|
-
// dependencies endpoint (`
|
|
131
|
+
// dependencies endpoint (`epic-relations.ts`, joshuafolkken/kit#1026), so the default route
|
|
128
132
|
// arrives here with the relations already in place. Three routes still reach a declared order with
|
|
129
133
|
// none recorded: an epic body written by hand, the manual fallback in `issue-template.md`, and
|
|
130
134
|
// `apply_relations` failing — it counts the refusals, reports that the body is intact and lets the
|
|
@@ -200,11 +204,11 @@ async function close_epic_with(epic_number: string, comment: string | undefined)
|
|
|
200
204
|
// `absent` and one of `unreadable` both post it, and the second says so.
|
|
201
205
|
async function close_epic(epic: EpicIssue): Promise<boolean> {
|
|
202
206
|
const epic_number = String(epic.number)
|
|
203
|
-
const state = await
|
|
207
|
+
const state = await epic_close_comment.read_close_comment_state(epic_number, epic)
|
|
204
208
|
|
|
205
209
|
if (state === 'unreadable') console.info(`ℹ️ Epic #${epic_number}${UNREADABLE_COMMENTS_NOTE}`)
|
|
206
210
|
|
|
207
|
-
const comment = state === 'present' ? undefined :
|
|
211
|
+
const comment = state === 'present' ? undefined : epic_close_comment.build_close_comment(epic)
|
|
208
212
|
|
|
209
213
|
return await close_epic_with(epic_number, comment)
|
|
210
214
|
}
|
|
@@ -256,7 +260,7 @@ async function close_epic_isolated(epic: EpicIssue, merged_number: number): Prom
|
|
|
256
260
|
try {
|
|
257
261
|
return await close_epic_when_complete(epic, merged_number)
|
|
258
262
|
} catch (error) {
|
|
259
|
-
const message =
|
|
263
|
+
const message = error_text.message_of(error)
|
|
260
264
|
|
|
261
265
|
console.info(`⚠️ Skipped epic #${String(epic.number)}: ${message}`)
|
|
262
266
|
|
|
@@ -265,7 +269,7 @@ async function close_epic_isolated(epic: EpicIssue, merged_number: number): Prom
|
|
|
265
269
|
}
|
|
266
270
|
|
|
267
271
|
function parents_of(open_epics: ReadonlyArray<EpicIssue>, child_number: number): Array<EpicIssue> {
|
|
268
|
-
return open_epics.filter((epic) =>
|
|
272
|
+
return open_epics.filter((epic) => epic_parse.has_child(epic.children, child_number))
|
|
269
273
|
}
|
|
270
274
|
|
|
271
275
|
// Closes one epic unless a cycle or a shared child already reached it — the closed number, or
|
|
@@ -351,7 +355,7 @@ async function resolve_and_close_safely(merged_number: number): Promise<void> {
|
|
|
351
355
|
try {
|
|
352
356
|
await resolve_and_close(merged_number)
|
|
353
357
|
} catch (error) {
|
|
354
|
-
const message =
|
|
358
|
+
const message = error_text.message_of(error)
|
|
355
359
|
|
|
356
360
|
console.info(`⚠️ Skipped the epic auto-close check: ${message}`)
|
|
357
361
|
}
|
|
@@ -376,12 +380,12 @@ async function close_completed_epics(input: {
|
|
|
376
380
|
await resolve_and_close_safely(merged_number)
|
|
377
381
|
}
|
|
378
382
|
|
|
379
|
-
const
|
|
383
|
+
const epic_close = {
|
|
380
384
|
close_completed_epics,
|
|
381
385
|
}
|
|
382
386
|
|
|
383
387
|
export {
|
|
384
|
-
|
|
388
|
+
epic_close,
|
|
385
389
|
close_completed_epics,
|
|
386
390
|
truncated_epic_list_note,
|
|
387
391
|
EPIC_LIST_LIMIT,
|
|
@@ -4,7 +4,6 @@ import { git_gh_api_path } from '#scripts/git/git-gh-api-path'
|
|
|
4
4
|
import { git_gh_exec } from '#scripts/git/git-gh-exec'
|
|
5
5
|
import { propagate_publish } from '#scripts/propagate/propagate-publish'
|
|
6
6
|
import { derive_versions_endpoint } from '#scripts/version/version-command-config'
|
|
7
|
-
import { execaSync } from 'execa'
|
|
8
7
|
import { load } from 'js-yaml'
|
|
9
8
|
import type { DependencyVerdict } from './epic-classify'
|
|
10
9
|
import type { EpicChild } from './epic-graph'
|
|
@@ -71,7 +70,6 @@ function is_published(repo: string, version: string): boolean {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
const GH_TIMEOUT_MS = 20_000
|
|
74
|
-
const SUCCESS_EXIT_CODE = 0
|
|
75
73
|
// The manifest arrives base64-encoded in the API's `content` field; `jq` decodes and reads it.
|
|
76
74
|
const VERSION_JQ = '.content | @base64d | fromjson | {version, private, workspaces} | tojson'
|
|
77
75
|
const PNPM_WORKSPACE_FILE = 'pnpm-workspace.yaml'
|
|
@@ -147,24 +145,20 @@ function pnpm_workspace_path(repo: string): string {
|
|
|
147
145
|
|
|
148
146
|
// A read that failed for any reason other than a missing file leaves the layout unknown.
|
|
149
147
|
function classify_workspace_failure(repo: string): WorkspaceCheck {
|
|
150
|
-
const
|
|
151
|
-
...git_gh_exec.direct_environment(),
|
|
152
|
-
reject: false,
|
|
153
|
-
timeout: GH_TIMEOUT_MS,
|
|
154
|
-
})
|
|
148
|
+
const status = git_gh_exec.exec_gh_api_status_sync(pnpm_workspace_path(repo), GH_TIMEOUT_MS)
|
|
155
149
|
|
|
156
|
-
return
|
|
150
|
+
return status === NOT_FOUND_STATUS ? 'single' : 'unreadable'
|
|
157
151
|
}
|
|
158
152
|
|
|
159
153
|
function read_pnpm_workspace(repo: string): WorkspaceCheck {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
const stdout = git_gh_exec.read_gh_api_sync({
|
|
155
|
+
path: pnpm_workspace_path(repo),
|
|
156
|
+
jq_filter: WORKSPACE_JQ,
|
|
157
|
+
timeout_ms: GH_TIMEOUT_MS,
|
|
164
158
|
})
|
|
165
|
-
if (
|
|
159
|
+
if (stdout === undefined) return classify_workspace_failure(repo)
|
|
166
160
|
|
|
167
|
-
return declares_packages(
|
|
161
|
+
return declares_packages(stdout) ? 'workspace' : 'single'
|
|
168
162
|
}
|
|
169
163
|
|
|
170
164
|
// Whether the repository declares a workspace. `workspaces` covers npm and yarn; pnpm keeps its
|
|
@@ -227,38 +221,27 @@ function to_manifest(repo: string, stdout: string): ManifestAnswer {
|
|
|
227
221
|
}
|
|
228
222
|
|
|
229
223
|
// Why the read failed, asked only when it did. The status line is the protocol rather than prose, so
|
|
230
|
-
// a missing manifest is told from a rate limit by matching a contract instead of a message
|
|
231
|
-
// `git_gh_exec.parse_status_line` rather than a second copy of it.
|
|
224
|
+
// a missing manifest is told from a rate limit by matching a contract instead of a message.
|
|
232
225
|
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
// what is missing is the file.
|
|
226
|
+
// The probe is `exec_gh_api_status_sync`, the synchronous twin of `exec_gh_api_status`: every caller
|
|
227
|
+
// down to `resolve_cross_repo` is synchronous, and turning the classifier, the resolver and
|
|
228
|
+
// `epic_classify` async for one probe would cost more than the twin (joshuafolkken/kit#2901). A 404
|
|
229
|
+
// here is trustworthy in a way a registry 404 is not: the repository's issues are already being read,
|
|
230
|
+
// so access is established and what is missing is the file.
|
|
239
231
|
function classify_manifest_failure(repo: string): ManifestAnswer {
|
|
240
|
-
const
|
|
241
|
-
...git_gh_exec.direct_environment(),
|
|
242
|
-
reject: false,
|
|
243
|
-
timeout: GH_TIMEOUT_MS,
|
|
244
|
-
})
|
|
232
|
+
const status = git_gh_exec.exec_gh_api_status_sync(manifest_path(repo), GH_TIMEOUT_MS)
|
|
245
233
|
|
|
246
|
-
return {
|
|
247
|
-
kind:
|
|
248
|
-
git_gh_exec.parse_status_line(probe.stdout) === NOT_FOUND_STATUS ? 'absent' : 'unreadable',
|
|
249
|
-
}
|
|
234
|
+
return { kind: status === NOT_FOUND_STATUS ? 'absent' : 'unreadable' }
|
|
250
235
|
}
|
|
251
236
|
|
|
252
237
|
function fetch_manifest(repo: string): ManifestAnswer {
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
238
|
+
const stdout = git_gh_exec.read_gh_api_sync({
|
|
239
|
+
path: manifest_path(repo),
|
|
240
|
+
jq_filter: VERSION_JQ,
|
|
241
|
+
timeout_ms: GH_TIMEOUT_MS,
|
|
257
242
|
})
|
|
258
243
|
|
|
259
|
-
return
|
|
260
|
-
? to_manifest(repo, result.stdout)
|
|
261
|
-
: classify_manifest_failure(repo)
|
|
244
|
+
return stdout === undefined ? classify_manifest_failure(repo) : to_manifest(repo, stdout)
|
|
262
245
|
}
|
|
263
246
|
|
|
264
247
|
// Read once per pass, for the reason `published_cache` holds the other half: every edge to one
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { format_replaced_relations } from './
|
|
3
|
-
import {
|
|
1
|
+
import { epic_parse, type DependencyLink } from './epic-parse'
|
|
2
|
+
import { format_replaced_relations } from './epic-reference'
|
|
3
|
+
import { epic_sections, type BodyLines, type SectionRange } from './epic-sections'
|
|
4
4
|
|
|
5
5
|
// Placing a decision record inside an epic body's `## Decisions` section.
|
|
6
6
|
//
|
|
@@ -45,10 +45,8 @@ function to_declaration_error(line_number: number): string {
|
|
|
45
45
|
function find_decision_error(record: string): string | undefined {
|
|
46
46
|
if (record.trim().length === 0) return EMPTY_RECORD
|
|
47
47
|
|
|
48
|
-
const input =
|
|
49
|
-
const [found] =
|
|
50
|
-
git_epic_parse.is_declaration_line(line),
|
|
51
|
-
)
|
|
48
|
+
const input = epic_sections.to_body_lines(record)
|
|
49
|
+
const [found] = epic_sections.find_indices(input, (line) => epic_parse.is_declaration_line(line))
|
|
52
50
|
|
|
53
51
|
return found === undefined ? undefined : to_declaration_error(found + FIRST_LINE_NUMBER)
|
|
54
52
|
}
|
|
@@ -81,8 +79,8 @@ function last_content_index(input: BodyLines, range: SectionRange): number {
|
|
|
81
79
|
// The section's *end* rather than its start: the entries are a log, and a reader looking for the most
|
|
82
80
|
// recent decision reads down. Nothing else in the body moves.
|
|
83
81
|
function append_decision(body: string, record: string): string {
|
|
84
|
-
const input =
|
|
85
|
-
const range =
|
|
82
|
+
const input = epic_sections.to_body_lines(body)
|
|
83
|
+
const range = epic_sections.find_section_range(input, DECISIONS_HEADING_PATTERN)
|
|
86
84
|
if (range === undefined) return to_new_section(input.lines, record).join('\n')
|
|
87
85
|
|
|
88
86
|
const at = last_content_index(input, range)
|
|
@@ -129,13 +127,13 @@ function format_decision_report(input: { total: number; failures: number }): str
|
|
|
129
127
|
}
|
|
130
128
|
|
|
131
129
|
// The heading a creation writes its reasoning under. Beside a pattern exactly as `DECISIONS_HEADING`
|
|
132
|
-
// is, and tied to `
|
|
130
|
+
// is, and tied to `epic_body`'s own literal by a test that reads a body the builder produced
|
|
133
131
|
// rather than by a string comparison (joshuafolkken/kit#1712).
|
|
134
132
|
const RATIONALE_HEADING_PATTERN = /^#{1,6}[ \t]+Split rationale\b/u
|
|
135
133
|
|
|
136
134
|
function read_section(body: string, heading: RegExp): string {
|
|
137
|
-
const input =
|
|
138
|
-
const range =
|
|
135
|
+
const input = epic_sections.to_body_lines(body)
|
|
136
|
+
const range = epic_sections.find_section_range(input, heading)
|
|
139
137
|
if (range === undefined) return ''
|
|
140
138
|
|
|
141
139
|
return input.lines.slice(range.start, range.end).join('\n')
|
|
@@ -159,7 +157,7 @@ function read_recorded_reasons(body: string | undefined): string {
|
|
|
159
157
|
].join('\n')
|
|
160
158
|
}
|
|
161
159
|
|
|
162
|
-
const
|
|
160
|
+
const epic_decision = {
|
|
163
161
|
DECISIONS_HEADING,
|
|
164
162
|
append_decision,
|
|
165
163
|
append_replacements,
|
|
@@ -168,4 +166,4 @@ const git_epic_decision = {
|
|
|
168
166
|
format_decision_report,
|
|
169
167
|
}
|
|
170
168
|
|
|
171
|
-
export {
|
|
169
|
+
export { epic_decision }
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { git_epic_parse, type ExternalChild } from '#scripts/git/git-epic-parse'
|
|
2
1
|
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
3
2
|
import type { IssueReadFailure } from '#scripts/git/git-gh-issue-read'
|
|
4
3
|
import { epic_cross_repo } from './epic-cross-repo'
|
|
5
4
|
import type { EpicChild, IssueReference } from './epic-graph'
|
|
6
5
|
import { epic_issue, type EpicIssue } from './epic-issue'
|
|
6
|
+
import { epic_parse, type ExternalChild } from './epic-parse'
|
|
7
7
|
import { epic_relation_recheck } from './epic-relation-recheck'
|
|
8
8
|
|
|
9
9
|
// Reading an epic and its children from GitHub.
|
|
@@ -73,8 +73,8 @@ async function fetch_epic_body(epic_number: number, scope?: string): Promise<Epi
|
|
|
73
73
|
return {
|
|
74
74
|
body,
|
|
75
75
|
body_failure: read.kind === 'read' ? undefined : read,
|
|
76
|
-
child_numbers:
|
|
77
|
-
external:
|
|
76
|
+
child_numbers: epic_parse.parse_task_list_issue_numbers(body),
|
|
77
|
+
external: epic_parse.parse_external_task_list_children(body),
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { DependencyLink } from '
|
|
1
|
+
import type { DependencyLink } from './epic-parse'
|
|
2
2
|
import {
|
|
3
3
|
format_dependency_link,
|
|
4
4
|
join_references,
|
|
5
5
|
to_issue_reference,
|
|
6
6
|
type IssueReference,
|
|
7
|
-
} from '
|
|
7
|
+
} from './epic-reference'
|
|
8
8
|
|
|
9
9
|
// The dependency graph an epic's children form, and the two ways it can be wrong.
|
|
10
10
|
//
|
|
@@ -275,5 +275,5 @@ const epic_graph = {
|
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
export type { EpicChild, GraphAnomaly, GraphAnomalyKind }
|
|
278
|
-
export type { IssueReference } from '
|
|
278
|
+
export type { IssueReference } from './epic-reference'
|
|
279
279
|
export { epic_graph }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { git_epic_parse } from '#scripts/git/git-epic-parse'
|
|
2
1
|
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
3
2
|
import { EPIC_LABEL, has_any_label } from '#scripts/git/issue-labels'
|
|
4
3
|
import { cutoff_of, type ScanCutoff } from '#scripts/git/listing-cutoff'
|
|
5
4
|
import { parse_json_array_or_undefined } from '#scripts/git/parse-json-array'
|
|
6
5
|
import type { OpenIssueData } from '#scripts/git/schemas'
|
|
7
6
|
import { z } from 'zod'
|
|
7
|
+
import { epic_parse } from './epic-parse'
|
|
8
8
|
|
|
9
9
|
// Which epic tracks which issue — the one answer, in one place (joshuafolkken/kit#1633).
|
|
10
10
|
//
|
|
@@ -40,7 +40,7 @@ function build_tracking_index(
|
|
|
40
40
|
const index = new Map<number, ReadonlyArray<number>>()
|
|
41
41
|
|
|
42
42
|
for (const epic of epics) {
|
|
43
|
-
for (const child of
|
|
43
|
+
for (const child of epic_parse.parse_task_list_issue_numbers(epic.body)) {
|
|
44
44
|
index.set(child, [...(index.get(child) ?? []), epic.number])
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { IssueReference } from '#scripts/git/git-epic-reference'
|
|
2
1
|
import { git_gh_issue_rest } from '#scripts/git/git-gh-issue-rest'
|
|
3
2
|
import { parse_json_object_safe } from '#scripts/git/parse-json-array'
|
|
4
3
|
import { blocked_by_schema } from '#scripts/git/schemas'
|
|
5
4
|
import { z } from 'zod'
|
|
5
|
+
import type { IssueReference } from './epic-reference'
|
|
6
6
|
|
|
7
7
|
// The shapes one issue is read as, and the argument every epic command takes.
|
|
8
8
|
//
|
|
@@ -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 }
|