@joshuafolkken/kit 1.1128.0 → 1.1166.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/agents/investigator.md +17 -0
- package/.claude/settings.json +5 -4
- package/.claude/skills/epic-commands/SKILL.md +27 -239
- package/.claude/skills/epic-commands/epic-bundle.md +115 -0
- package/.claude/skills/epic-commands/execution-waves.md +126 -0
- package/.claude/skills/workflow-commands/SKILL.md +48 -194
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-child.md +12 -123
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +11 -60
- package/.claude/skills/workflow-commands/backlogrun-park.md +10 -8
- package/.claude/skills/workflow-commands/backlogrun-progress.md +19 -175
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +155 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +64 -144
- package/.claude/skills/workflow-commands/backlogrun.md +5 -4
- package/.claude/skills/workflow-commands/chain-rule.md +3 -2
- package/.claude/skills/workflow-commands/delegation.md +7 -3
- package/.claude/skills/workflow-commands/entry-sequence.md +62 -0
- package/.claude/skills/workflow-commands/followup.md +104 -181
- package/.claude/skills/workflow-commands/fullrun-steps.md +8 -9
- package/.claude/skills/workflow-commands/fullrun.md +16 -73
- package/.claude/skills/workflow-commands/halfrun.md +10 -36
- package/.claude/skills/workflow-commands/into-target.md +4 -4
- package/.claude/skills/workflow-commands/issue-comments.md +7 -8
- package/.claude/skills/workflow-commands/issue-scout.md +6 -6
- package/.claude/skills/workflow-commands/kickoff.md +6 -6
- package/.claude/skills/workflow-commands/needs-human-review.md +6 -6
- package/.claude/skills/workflow-commands/observation-filing.md +21 -172
- package/.claude/skills/workflow-commands/observation-ledger.md +150 -0
- package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -93
- package/.claude/skills/workflow-commands/prerequisite.md +6 -6
- package/.claude/skills/workflow-commands/progress-watcher.md +90 -0
- package/.claude/skills/workflow-commands/prrun.md +5 -5
- package/.claude/skills/workflow-commands/retrospective.md +9 -9
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/.claude/skills/workflow-commands/target-repository.md +2 -2
- package/.claude/skills/workflow-commands/working-tree-hold.md +7 -5
- package/.codex/hooks.json +4 -4
- package/AGENTS.md +1 -6
- package/CLAUDE.md +58 -120
- package/GEMINI.md +1 -6
- package/SECURITY.md +1 -0
- package/dist/CLAUDE.md +58 -120
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-MKVGBQPA.js → chunk-5KTCMCOT.js} +14 -1
- package/dist/hooks/{chunk-NXYMNPUQ.js → chunk-A6P7PN7P.js} +59 -21
- package/dist/hooks/{chunk-77J7IEQM.js → chunk-ANJ2WGFD.js} +1 -1
- package/dist/hooks/{chunk-4JZDP5X5.js → chunk-DYAVAM6S.js} +572 -127
- package/dist/hooks/{chunk-7HVB7SZG.js → chunk-ENA7P5FB.js} +4 -4
- package/dist/hooks/{chunk-M7QKEKWO.js → chunk-GU5MATVE.js} +214 -85
- package/dist/hooks/chunk-GYNGP7HI.js +56 -0
- package/dist/hooks/{chunk-5SD2YM7Z.js → chunk-URJA6XY4.js} +4 -4
- package/dist/hooks/{chunk-XYUIQOW3.js → chunk-V2BTOZHT.js} +6 -4
- package/dist/hooks/{chunk-AIOIOMPX.js → chunk-ZDZ555YH.js} +225 -78
- 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/inputs.json +186 -184
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +11 -10
- package/dist/hooks/stop-guard.js +5 -5
- package/dist/josh.js +33 -3
- package/dist/version/index.js +5 -1
- package/package.json +7 -4
- package/prompts/collaboration-workflow/file-edits.md +11 -10
- package/prompts/collaboration-workflow/gh-rest.md +2 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/issue-citation.md +5 -9
- package/prompts/collaboration-workflow/issue-template.md +40 -115
- package/prompts/collaboration-workflow/operating-rules.md +4 -5
- package/prompts/collaboration-workflow/output-bounds.md +12 -78
- package/prompts/collaboration-workflow/overview.md +7 -13
- package/prompts/collaboration-workflow/plan-comment.md +11 -39
- package/prompts/collaboration-workflow/principles.md +3 -4
- package/prompts/collaboration-workflow/report-format.md +28 -74
- package/prompts/collaboration-workflow/residency.md +12 -109
- package/prompts/collaboration-workflow/rule-delivery.md +98 -97
- package/prompts/collaboration-workflow/shell-body.md +3 -13
- package/prompts/collaboration-workflow/turn-batching.md +14 -88
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -14
- package/prompts/collaboration-workflow/wip-cap.md +6 -45
- package/prompts/collaboration-workflow.md +1 -13
- package/prompts/review-rubric.md +8 -71
- package/prompts/review.md +25 -55
- package/prompts/testing-guide.md +18 -67
- package/scripts/agent/agent-role-profile.ts +1 -1
- package/scripts/agent/codex-hooks.ts +3 -12
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-cli.ts +4 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +10 -1
- package/scripts/backlog/backlog-offer.ts +6 -6
- package/scripts/behavior/behavior-rules.ts +11 -4
- package/scripts/delegation/delegation-policy.ts +9 -1
- package/scripts/delegation/investigation-guard.ts +1 -1
- package/scripts/delegation/investigation-reads.ts +109 -23
- package/scripts/discovery/repo-discovery.ts +21 -7
- package/scripts/discovery/repo-origin.ts +26 -0
- package/scripts/discovery/repo-party.ts +42 -1
- package/scripts/document/backlogrun-parent-read-set.ts +14 -15
- package/scripts/document/capped-print.ts +74 -0
- package/scripts/document/document-byte-budget.ts +34 -20
- package/scripts/document/document-section-cli.ts +4 -3
- package/scripts/document/entry-read-budget.ts +21 -9
- package/scripts/document/lane-child-read-set.ts +12 -14
- package/scripts/document/read-set-cli.ts +4 -4
- package/scripts/document/read-set-point-of-use.ts +10 -2
- package/scripts/document/resident-budget.ts +47 -0
- package/scripts/epic/epic-shape.ts +10 -13
- package/scripts/epic/epic-solo.ts +1 -1
- package/scripts/exports/unused-members-cli.ts +7 -3
- package/scripts/gate/scoped-green.ts +25 -14
- package/scripts/gh/git-gh-api-path.ts +7 -0
- package/scripts/hooks/pretool-guard-cli.ts +2 -2
- package/scripts/hooks/pretool-guard.ts +45 -2
- package/scripts/hooks/run-hook.sh +41 -0
- package/scripts/init/claude-kit-only-settings.ts +29 -0
- package/scripts/init/hook-command-rewrite.ts +18 -35
- package/scripts/init/hook-launch.ts +21 -32
- package/scripts/init/init-copy-content.ts +8 -2
- package/scripts/init/initial-commit.ts +24 -0
- package/scripts/init/start-exec.ts +16 -3
- package/scripts/init/start-steps.ts +8 -12
- package/scripts/issue/issue-auto-ok.ts +95 -0
- package/scripts/issue/issue-file-cli.ts +53 -7
- package/scripts/issue/issue-file.ts +28 -5
- package/scripts/issue/issue-labels.ts +12 -12
- package/scripts/issue/issue-rank.ts +1 -1
- package/scripts/issue/issue-read-cli.ts +4 -3
- package/scripts/issue/issue-read.ts +2 -2
- package/scripts/issue/issue-report-failures.ts +6 -2
- package/scripts/issue/issue-scout-cli.ts +2 -2
- package/scripts/issue/issue-state-cli.ts +2 -2
- package/scripts/issue/issue-state.ts +2 -2
- package/scripts/issue/issue-wip.ts +70 -0
- package/scripts/josh/hook-decision.ts +17 -0
- package/scripts/josh/josh-commands-development.ts +10 -0
- package/scripts/josh/josh-commands-issue.ts +2 -2
- package/scripts/josh/josh-commands-lane.ts +1 -1
- package/scripts/josh/josh-commands-project.ts +7 -0
- package/scripts/lane/lane-await.ts +46 -2
- package/scripts/lane/lane-cli.ts +11 -13
- package/scripts/lines/bytes-command.ts +29 -6
- package/scripts/observations/observation-ledger-line.ts +1 -1
- package/scripts/release/github-release-environment.ts +1 -0
- package/scripts/release/github-release.ts +103 -17
- package/scripts/release/release-progress.ts +167 -0
- package/scripts/release/release-publish.ts +19 -8
- package/scripts/release/release-tag.ts +22 -8
- package/scripts/rules/decision-oracle-stage.ts +1 -1
- package/scripts/rules/decision-oracle.ts +6 -6
- package/scripts/rules/delivered-rules.ts +11 -11
- package/scripts/rules/direct-pr-create.ts +110 -0
- package/scripts/rules/file-body.ts +98 -42
- package/scripts/rules/filing-cap.ts +2 -2
- package/scripts/rules/filing-offer.ts +1 -1
- package/scripts/rules/gh-api.ts +18 -1
- package/scripts/rules/implementation-cut.ts +3 -1
- package/scripts/rules/lane-background.ts +67 -10
- package/scripts/rules/permission-guards.ts +10 -2
- package/scripts/rules/stop-rules.ts +4 -4
- package/scripts/rules/test-declared-commit.ts +1 -1
- package/scripts/rules/third-party-write.ts +56 -12
- package/scripts/run/run-carry-cli.ts +3 -1
- package/scripts/run/run-carry-conversation.ts +94 -0
- package/scripts/run/run-carry-reclaim.ts +37 -0
- package/scripts/run/run-carry.ts +21 -5
- package/scripts/run/run-cut-handoff.ts +21 -1
- package/scripts/run/run-entry-cli.ts +11 -3
- package/scripts/run/run-hold-cli.ts +16 -2
- package/scripts/run/run-label.ts +50 -0
- package/scripts/run/run-merge-steps.ts +12 -4
- package/scripts/run/run-merge.ts +1 -1
- package/scripts/run/run-prep-cli.ts +6 -2
- package/scripts/run/run-prep.ts +17 -0
- package/scripts/run/run-ship-preflight.ts +34 -3
- package/scripts/run/run-ship-review-steps.ts +1 -1
- package/scripts/run/run-ship-review.ts +26 -5
- package/scripts/run/run-step-cli.ts +20 -7
- package/scripts/run/run-wake-driver.ts +24 -1
- package/scripts/run/run-wake-session.ts +2 -1
- package/scripts/test/test-declared-logic.ts +1 -1
- package/scripts/test/test-related.ts +1 -6
- package/scripts/test/test-unit-guard.ts +21 -7
- package/scripts/time-runtime/time-batch-guard.ts +52 -16
- package/scripts/time-runtime/time-density-hook.ts +20 -2
- package/scripts/time-runtime/time-density.ts +4 -3
- package/scripts/time-runtime/time-markers.ts +15 -4
- package/scripts/time-runtime/time-spans.ts +4 -0
- package/scripts/version/npm-registry.ts +11 -1
- package/templates/workflows/ci.yml +9 -1
- package/templates/workflows/github-release.yml +13 -25
- package/.claude/skills/workflow-commands/followup-reference.md +0 -118
- package/dist/hooks/chunk-QVLT52OV.js +0 -464
|
@@ -7,51 +7,40 @@ import { GIT_LOCATION_VARIABLES } from '#scripts/git/git-location-environment'
|
|
|
7
7
|
//
|
|
8
8
|
// **The command starts from the project root, never from wherever the session happens to be**
|
|
9
9
|
// (joshuafolkken/kit#2984). Every path after the prefix is relative, so a hook fired from a
|
|
10
|
-
// subdirectory would otherwise miss its
|
|
11
|
-
// hook exports cleared first
|
|
12
|
-
//
|
|
10
|
+
// subdirectory would otherwise miss its script. The root is git's, resolved with the location
|
|
11
|
+
// variables a git hook exports cleared first, and resolved with them only when that fails — git
|
|
12
|
+
// metadata outside the work tree. The consumer rewrite (`hook-command-rewrite.ts`) keeps this prefix,
|
|
13
|
+
// so both sides stay one mechanism for Claude Code and Codex alike.
|
|
13
14
|
//
|
|
14
|
-
// **
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// source. **An `if`/`else`, never `&&`/`||`**: a guard's refusal is a non-zero exit, and a
|
|
18
|
-
// `node … || pnpm josh …` chain would read that refusal as a missing bundle and run the hook twice.
|
|
15
|
+
// **Everything after the root is one script** (`scripts/hooks/run-hook.sh`, joshuafolkken/kit#3184):
|
|
16
|
+
// clearing the location variables for the hook itself, the bundle-ready gate and the live-source
|
|
17
|
+
// fallback live there once, so each hook command names only its hook.
|
|
19
18
|
|
|
20
19
|
const HOOK_DIST_DIR = 'dist/hooks'
|
|
20
|
+
const RUN_HOOK_SCRIPT = 'scripts/hooks/run-hook.sh'
|
|
21
21
|
const UNSET_OPTIONS = GIT_LOCATION_VARIABLES.map((name) => `-u ${name}`).join(' ')
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
// every hook's stderr.
|
|
25
|
-
const BUNDLE_READY_GATE =
|
|
26
|
-
'node --disable-warning=ExperimentalWarning scripts/hooks/hook-bundle-ready.ts'
|
|
22
|
+
const ROOT_LOOKUP = 'git rev-parse --show-toplevel'
|
|
23
|
+
const PROJECT_ROOT_PREFIX = `project_root="$(env ${UNSET_OPTIONS} ${ROOT_LOOKUP} 2>/dev/null || ${ROOT_LOOKUP})" && cd "$project_root" && `
|
|
27
24
|
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
function
|
|
31
|
-
return
|
|
25
|
+
// `name` is the hook's bundle under `dist/hooks` without `.js` (e.g. `pretool-guard`), followed by any
|
|
26
|
+
// arguments it takes (e.g. `codex-hook-adapter pretool`).
|
|
27
|
+
function hook_launch_command(name: string): string {
|
|
28
|
+
return `${PROJECT_ROOT_PREFIX}sh ${RUN_HOOK_SCRIPT} ${name}`
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
//
|
|
35
|
-
// `
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return `${PROJECT_ROOT_PREFIX}${launch_command(BUNDLE_READY_GATE, primary, fallback_command)}`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// `bundle` is the hook's file under `dist/hooks` (e.g. `pretool-guard.js`); `command` is the josh
|
|
43
|
-
// subcommand the fallback runs (e.g. `pretool:guard`).
|
|
44
|
-
function hook_launch_command(bundle: string, command: string): string {
|
|
45
|
-
return bundle_launch_command(bundle, `pnpm josh ${command}`)
|
|
31
|
+
// The josh subcommand the launcher falls back to for hook `name`: its first `-` becomes `:`
|
|
32
|
+
// (`pretool-guard` → `pretool:guard`). The launcher derives it in shell; this is that rule for the tests
|
|
33
|
+
// that hold every wired hook to a subcommand josh has.
|
|
34
|
+
function fallback_command(name: string): string {
|
|
35
|
+
return name.replace('-', ':')
|
|
46
36
|
}
|
|
47
37
|
|
|
48
38
|
const hook_launch = {
|
|
49
|
-
|
|
50
|
-
bundle_launch_command,
|
|
39
|
+
fallback_command,
|
|
51
40
|
HOOK_DIST_DIR,
|
|
52
41
|
hook_launch_command,
|
|
53
|
-
launch_command,
|
|
54
42
|
PROJECT_ROOT_PREFIX,
|
|
43
|
+
RUN_HOOK_SCRIPT,
|
|
55
44
|
}
|
|
56
45
|
|
|
57
46
|
export { hook_launch }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { managed_marker_logic } from '#scripts/managed-marker/managed-marker-logic'
|
|
2
2
|
import { workflow_pin_logic } from '#scripts/sync/workflow-pin-logic'
|
|
3
3
|
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
4
|
+
import { claude_kit_only_settings } from './claude-kit-only-settings'
|
|
4
5
|
import { claude_plugin_config } from './claude-plugin-config'
|
|
5
6
|
import { hook_command_rewrite } from './hook-command-rewrite'
|
|
6
7
|
import { init_logic } from './init-logic'
|
|
@@ -19,7 +20,8 @@ import { init_logic } from './init-logic'
|
|
|
19
20
|
* workflow-pin-logic.ts for why the template refs are not authoritative, and
|
|
20
21
|
* managed-marker-logic.ts for why the stamp is on the file rather than in a list.
|
|
21
22
|
*
|
|
22
|
-
* The consumer's `.claude/settings.json` gets
|
|
23
|
+
* The consumer's `.claude/settings.json` gets three further passes. The keys kit sets only for its
|
|
24
|
+
* own sessions are removed (joshuafolkken/kit#3140, see claude-kit-only-settings.ts). The kit plugin's marketplace and
|
|
23
25
|
* `enabledPlugins` declaration is injected, because the skills that used to be copied now ship as the
|
|
24
26
|
* `kit` plugin and load from that declaration (joshuafolkken/kit#1879). And its hook commands are
|
|
25
27
|
* rewritten from `pnpm josh` to the published bundle invoked directly with node
|
|
@@ -36,10 +38,14 @@ function transform_copied_content(destination_path: string, content: string): st
|
|
|
36
38
|
with_pins,
|
|
37
39
|
KIT_PACKAGE_NAME,
|
|
38
40
|
)
|
|
39
|
-
const
|
|
41
|
+
const with_kit_only_stripped = claude_kit_only_settings.apply_kit_only_strip_for_destination(
|
|
40
42
|
destination_path,
|
|
41
43
|
with_marker,
|
|
42
44
|
)
|
|
45
|
+
const with_plugin = claude_plugin_config.apply_plugin_config_for_destination(
|
|
46
|
+
destination_path,
|
|
47
|
+
with_kit_only_stripped,
|
|
48
|
+
)
|
|
43
49
|
|
|
44
50
|
return hook_command_rewrite.apply_hook_command_rewrite_for_destination(
|
|
45
51
|
destination_path,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { start_exec } from './start-exec'
|
|
2
|
+
|
|
3
|
+
// The first commit of a new repository, shared by `josh start` and `josh dogfood:commit` so neither
|
|
4
|
+
// keeps a copy of how it is made.
|
|
5
|
+
|
|
6
|
+
const DEFAULT_BRANCH = 'main'
|
|
7
|
+
|
|
8
|
+
function create_repository(root: string): void {
|
|
9
|
+
start_exec.run('git', ['init', `--initial-branch=${DEFAULT_BRANCH}`], root)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function commit_all(root: string): void {
|
|
13
|
+
start_exec.run('git', ['add', '--all'], root)
|
|
14
|
+
// The first commit of a new repository has no branch to come from, so the hook that keeps commits
|
|
15
|
+
// off main — installed by the initialize step in a full project — would refuse the only commit
|
|
16
|
+
// that has to land there.
|
|
17
|
+
start_exec.run('git', ['commit', '--no-verify', '--message', 'Initial commit'], root)
|
|
18
|
+
// A repository created by an older `git init` may name its unborn branch `master`.
|
|
19
|
+
start_exec.run('git', ['branch', '--move', '--force', DEFAULT_BRANCH], root)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const initial_commit = { create_repository, commit_all }
|
|
23
|
+
|
|
24
|
+
export { initial_commit }
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { git_gh_exec } from '#scripts/gh/git-gh-exec'
|
|
2
|
+
import { git_location_environment } from '#scripts/git/git-location-environment'
|
|
2
3
|
import { execaSync } from 'execa'
|
|
3
4
|
|
|
4
5
|
// The three spawn shapes every `josh start` step uses, shared by `start-steps.ts`, the setup pull
|
|
5
6
|
// request step and the `josh init` hint so none of them keeps a copy of its own.
|
|
7
|
+
//
|
|
8
|
+
// The three git-driving shapes below act on `root`, so `cwd` has to mean it: an inherited `GIT_DIR` and friends beat `cwd`
|
|
9
|
+
// and would carry a `git init` or commit into whichever repository a calling hook was firing in
|
|
10
|
+
// (joshuafolkken/kit#3151).
|
|
11
|
+
function root_environment(): Record<string, string | undefined> {
|
|
12
|
+
return git_location_environment.location_free_environment()
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
function succeeds(command: string, args: ReadonlyArray<string>, root: string): boolean {
|
|
7
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
execaSync(command, args, { cwd: root, env: root_environment(), reject: false }).exitCode === 0
|
|
18
|
+
)
|
|
8
19
|
}
|
|
9
20
|
|
|
10
21
|
function read_output(
|
|
@@ -12,7 +23,7 @@ function read_output(
|
|
|
12
23
|
args: ReadonlyArray<string>,
|
|
13
24
|
root: string,
|
|
14
25
|
): string | undefined {
|
|
15
|
-
const result = execaSync(command, args, { cwd: root, reject: false })
|
|
26
|
+
const result = execaSync(command, args, { cwd: root, env: root_environment(), reject: false })
|
|
16
27
|
|
|
17
28
|
return result.exitCode === 0 ? result.stdout.trim() : undefined
|
|
18
29
|
}
|
|
@@ -20,7 +31,9 @@ function read_output(
|
|
|
20
31
|
// Every step spawned here dials GitHub directly, past a scanner's loopback proxy, the same as every
|
|
21
32
|
// other `gh` spawn under scripts/ (joshuafolkken/kit#2436); a local `git` step is unaffected by it.
|
|
22
33
|
function run(command: string, args: ReadonlyArray<string>, root: string): void {
|
|
23
|
-
|
|
34
|
+
const environment = { ...root_environment(), ...git_gh_exec.direct_environment().env }
|
|
35
|
+
|
|
36
|
+
execaSync(command, args, { env: environment, cwd: root, stdio: 'inherit' })
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
// A caller's own initialize command stands in for the in-process `josh init`, so it keeps the
|
|
@@ -6,6 +6,7 @@ import { self_sync_guard } from '#scripts/self-sync-guard/self-sync-guard-logic'
|
|
|
6
6
|
import { parseCommandString } from 'execa'
|
|
7
7
|
import { main as init_main } from './init'
|
|
8
8
|
import { PACKAGE_DIR } from './init-paths'
|
|
9
|
+
import { initial_commit } from './initial-commit'
|
|
9
10
|
import { kit_setup_state } from './kit-setup-state'
|
|
10
11
|
import type { ProjectProfile, ProjectShape } from './project-profile'
|
|
11
12
|
import { start_exec } from './start-exec'
|
|
@@ -23,7 +24,6 @@ interface RunContext extends StepContext {
|
|
|
23
24
|
baseline: ReadonlyArray<string> | undefined
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
const DEFAULT_BRANCH = 'main'
|
|
27
27
|
const GH_INSTALL_HINT =
|
|
28
28
|
'GitHub CLI (gh) is not installed. Install it from https://cli.github.com/, run gh auth login, then run josh start again. Nothing was changed.'
|
|
29
29
|
const GH_AUTH_HINT =
|
|
@@ -70,7 +70,7 @@ function self_run_refusal(root: string): string | undefined {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function create_git_repository(context: StepContext): void {
|
|
73
|
-
|
|
73
|
+
initial_commit.create_repository(context.root)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// The same setup `josh init` runs, called rather than copied, with the profile the user confirmed.
|
|
@@ -90,13 +90,7 @@ function initialize(context: StepContext): void {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function commit_all(context: StepContext): void {
|
|
93
|
-
|
|
94
|
-
// The first commit of a new repository has no branch to come from, so the hook that keeps commits
|
|
95
|
-
// off main — installed by the initialize step in a full project — would refuse the only commit
|
|
96
|
-
// that has to land there.
|
|
97
|
-
run('git', ['commit', '--no-verify', '--message', 'Initial commit'], context.root)
|
|
98
|
-
// A repository created by an older `git init` may name its unborn branch `master`.
|
|
99
|
-
run('git', ['branch', '--move', '--force', DEFAULT_BRANCH], context.root)
|
|
93
|
+
initial_commit.commit_all(context.root)
|
|
100
94
|
}
|
|
101
95
|
|
|
102
96
|
function create_github_repository(context: StepContext): void {
|
|
@@ -119,13 +113,15 @@ async function open_setup_pull_request(context: RunContext): Promise<void> {
|
|
|
119
113
|
await start_setup_pr.open(context.root, context.baseline)
|
|
120
114
|
}
|
|
121
115
|
|
|
122
|
-
// What was already changed before
|
|
123
|
-
//
|
|
116
|
+
// What was already changed before the setup ran, read whenever a setup pull request will follow, so
|
|
117
|
+
// it can tell the setup's files from the user's own (#2872). Kit's own `josh init` needs it as much as
|
|
118
|
+
// a caller's command: its `josh format` rewrites the project's own sources, which kit cannot name, and
|
|
119
|
+
// leaving them out failed the pull request's Prettier check (joshuafolkken/kit#3136).
|
|
124
120
|
function baseline_of(
|
|
125
121
|
steps: ReadonlyArray<StartStep>,
|
|
126
122
|
context: StepContext,
|
|
127
123
|
): ReadonlyArray<string> | undefined {
|
|
128
|
-
if (
|
|
124
|
+
if (!steps.includes('setup_pr')) return undefined
|
|
129
125
|
|
|
130
126
|
return start_setup_pr.changed_paths(context.root)
|
|
131
127
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { git_branch } from '#scripts/git/git-branch'
|
|
2
|
+
import { git_command } from '#scripts/git/git-command'
|
|
3
|
+
import { run_carry } from '#scripts/run/run-carry'
|
|
4
|
+
import { issue_file } from './issue-file'
|
|
5
|
+
import { AUTO_OK_LABEL, has_label_name } from './issue-labels'
|
|
6
|
+
import { issue_state_cli } from './issue-state-cli'
|
|
7
|
+
|
|
8
|
+
// Whether `josh issue:file` applies `auto-ok` to the Issue it files (joshuafolkken/kit#3213). Work
|
|
9
|
+
// found during an unattended run used to wait in the backlog until a person labelled it, so the
|
|
10
|
+
// filing now computes the answer instead of leaving it to the agent's memory: an Issue filed while a
|
|
11
|
+
// `backlogrun` carry record is live, or from a branch whose Issue carries `auto-ok`, is opted in by
|
|
12
|
+
// default. `--no-auto-ok` is the one exception, declared for an Issue that needs a person's judgement
|
|
13
|
+
// (a Tier B toss-up or a Tier C action). A filing to another repository is never opted in: that
|
|
14
|
+
// repository's own `backlogrun` would implement and merge it though nobody opted that repository in.
|
|
15
|
+
|
|
16
|
+
interface AutoOkSignals {
|
|
17
|
+
is_opted_out: boolean
|
|
18
|
+
is_carried: boolean
|
|
19
|
+
// The labels of the Issue the current branch names; `undefined` when the branch names none or the
|
|
20
|
+
// Issue could not be read.
|
|
21
|
+
branch_labels: ReadonlyArray<string> | undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface AutoOkDecision {
|
|
25
|
+
is_applied: boolean
|
|
26
|
+
reason: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const OPTED_OUT: AutoOkDecision = { is_applied: false, reason: '--no-auto-ok declared' }
|
|
30
|
+
const CROSS_REPOSITORY: AutoOkDecision = {
|
|
31
|
+
is_applied: false,
|
|
32
|
+
reason: 'the Issue is filed to another repository',
|
|
33
|
+
}
|
|
34
|
+
const CARRIED: AutoOkDecision = { is_applied: true, reason: 'a backlogrun carry record is live' }
|
|
35
|
+
const BRANCH_OPTED_IN: AutoOkDecision = {
|
|
36
|
+
is_applied: true,
|
|
37
|
+
reason: 'the Issue this branch works on carries auto-ok',
|
|
38
|
+
}
|
|
39
|
+
const NOT_OPTED_IN: AutoOkDecision = {
|
|
40
|
+
is_applied: false,
|
|
41
|
+
reason: 'no backlogrun carry record is live and the branch names no auto-ok Issue',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The opt-out wins over every signal, then the carry record, then the branch's Issue.
|
|
45
|
+
function decide(signals: AutoOkSignals): AutoOkDecision {
|
|
46
|
+
if (signals.is_opted_out) return OPTED_OUT
|
|
47
|
+
if (signals.is_carried) return CARRIED
|
|
48
|
+
if (has_label_name(signals.branch_labels ?? [], AUTO_OK_LABEL)) return BRANCH_OPTED_IN
|
|
49
|
+
|
|
50
|
+
return NOT_OPTED_IN
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function line_of(decision: AutoOkDecision): string {
|
|
54
|
+
return `auto-ok: ${decision.is_applied ? 'applied' : 'not applied'} — ${decision.reason}`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Read from the common git directory, so a lane sees the record its batch wrote in the main tree.
|
|
58
|
+
async function is_carried(): Promise<boolean> {
|
|
59
|
+
const directory = await run_carry.repository_directory()
|
|
60
|
+
|
|
61
|
+
if (directory === undefined) return false
|
|
62
|
+
|
|
63
|
+
return run_carry.read_carry(run_carry.carry_path(directory)).kind === 'carried'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// An unreadable Issue is read as no labels: the filing goes on without `auto-ok` rather than failing.
|
|
67
|
+
// The branch names an Issue of `current`, read there by name: a filing to another repository has
|
|
68
|
+
// already pointed `GH_REPO` at its target, which would otherwise answer with the target's Issue.
|
|
69
|
+
async function branch_labels(current: string): Promise<ReadonlyArray<string> | undefined> {
|
|
70
|
+
const issue_number = git_branch.issue_from_branch(await git_command.branch())
|
|
71
|
+
|
|
72
|
+
if (issue_number === undefined) return undefined
|
|
73
|
+
const read = await issue_state_cli.read_issue(String(issue_number), current)
|
|
74
|
+
|
|
75
|
+
return read.kind === 'state' ? read.state.labels : undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Each read is skipped once an earlier signal has already decided the answer; a cross-repository
|
|
79
|
+
// filing is refused before any signal is read.
|
|
80
|
+
async function resolve(
|
|
81
|
+
is_opted_out: boolean,
|
|
82
|
+
target: string,
|
|
83
|
+
current: string,
|
|
84
|
+
): Promise<AutoOkDecision> {
|
|
85
|
+
if (is_opted_out) return OPTED_OUT
|
|
86
|
+
if (!issue_file.is_same_repository(target, current)) return CROSS_REPOSITORY
|
|
87
|
+
if (await is_carried()) return CARRIED
|
|
88
|
+
|
|
89
|
+
return decide({ is_opted_out, is_carried: false, branch_labels: await branch_labels(current) })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const issue_auto_ok = { decide, line_of, resolve }
|
|
93
|
+
|
|
94
|
+
export type { AutoOkDecision, AutoOkSignals }
|
|
95
|
+
export { issue_auto_ok }
|
|
@@ -5,24 +5,30 @@ import { repo_party } from '#scripts/discovery/repo-party'
|
|
|
5
5
|
import { epic_bundle_cli } from '#scripts/epic/epic-bundle-cli'
|
|
6
6
|
import { git_gh_command } from '#scripts/gh/git-gh-command'
|
|
7
7
|
import { git_gh_exec } from '#scripts/gh/git-gh-exec'
|
|
8
|
+
import { git_gh_issue_list, MAX_SCANNED } from '#scripts/gh/git-gh-issue-list'
|
|
8
9
|
import { git_gh_issue_write } from '#scripts/gh/git-gh-issue-write'
|
|
9
10
|
import { github_issue_url } from '#scripts/gh/github-issue-url'
|
|
10
11
|
import { error_text } from '#scripts/lib/error-message'
|
|
12
|
+
import { repository_labels } from '#scripts/repo/repository-labels'
|
|
13
|
+
import { issue_auto_ok } from './issue-auto-ok'
|
|
11
14
|
import { issue_file, type FileArguments } from './issue-file'
|
|
12
15
|
import { issue_lint_cli } from './issue-lint-cli'
|
|
13
16
|
import { issue_scout_cli } from './issue-scout-cli'
|
|
17
|
+
import { issue_wip } from './issue-wip'
|
|
14
18
|
|
|
15
19
|
// `josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>]…
|
|
16
|
-
// [--repo <owner/repo>] [--distinct <N,…>]` — file an Issue with every
|
|
17
|
-
// (joshuafolkken/kit#2808): the third-party refusal, the body lint, the
|
|
18
|
-
//
|
|
20
|
+
// [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok]` — file an Issue with every
|
|
21
|
+
// filing step run in order (joshuafolkken/kit#2808): the third-party refusal, the body lint, the
|
|
22
|
+
// `## Origin` check for another repository, the WIP cap count (joshuafolkken/kit#3181), the duplicate
|
|
23
|
+
// scout, the missing workflow labels created (joshuafolkken/kit#3176), the `auto-ok` decision
|
|
24
|
+
// (joshuafolkken/kit#3213), the create call carrying every label, and `epic:bundle` after it.
|
|
19
25
|
// A direct `gh api …/issues` filing is refused by the `direct-filing` delivered rule and pointed here.
|
|
20
26
|
|
|
21
27
|
const SUCCESS_EXIT_CODE = 0
|
|
22
28
|
const FAILURE_EXIT_CODE = 1
|
|
23
29
|
const ARGV_OFFSET = 2
|
|
24
30
|
const USAGE =
|
|
25
|
-
'Usage: josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <tier-a|split|interrupt|review-cap>] [--label <name>]… [--repo <owner/repo>] [--distinct <N,…>]'
|
|
31
|
+
'Usage: josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <tier-a|split|interrupt|review-cap>] [--label <name>]… [--repo <owner/repo>] [--distinct <N,…>] [--over-cap] [--no-auto-ok]'
|
|
26
32
|
const UNKNOWN_REPO_MESSAGE =
|
|
27
33
|
'Could not read this repository from `git remote`, so the filing has no repository to compare against — check `gh auth status`.'
|
|
28
34
|
const THIRD_PARTY_MESSAGE =
|
|
@@ -68,6 +74,37 @@ function unacknowledged_message(numbers: ReadonlyArray<number>): string {
|
|
|
68
74
|
return `✖ duplicate candidate(s) ${listed} not declared separate — read each (\`issue-fold-existing.md\`); fold into a duplicate, or reissue with \`--distinct ${listed}\` when each is a separate deliverable.`
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
// The target's open Issues, pull requests excluded by the listing itself; `undefined` when unreadable.
|
|
78
|
+
async function open_count(target: string): Promise<number | undefined> {
|
|
79
|
+
const outcome = await git_gh_issue_list.issue_list({
|
|
80
|
+
json_fields: 'number',
|
|
81
|
+
limit: MAX_SCANNED,
|
|
82
|
+
repo: target,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return outcome.json === undefined ? undefined : issue_wip.count_of(outcome.json)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// The count is printed whatever it answers, and the filing is held only past the cap with no
|
|
89
|
+
// exemption declared. An unreadable count warns and goes on: the cap makes growth visible, and a
|
|
90
|
+
// listing outage is not a reason to stop the run a blocked filing exists for.
|
|
91
|
+
async function is_wip_clear(filing: Filing): Promise<boolean> {
|
|
92
|
+
const count = await open_count(filing.target)
|
|
93
|
+
|
|
94
|
+
if (count === undefined) {
|
|
95
|
+
console.error(issue_wip.UNREAD_MESSAGE)
|
|
96
|
+
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const verdict = issue_wip.verdict_of(count, filing.args)
|
|
101
|
+
|
|
102
|
+
console.info(issue_wip.count_line(count, filing.target, verdict))
|
|
103
|
+
if (verdict === issue_wip.HELD) console.error(issue_wip.HELD_MESSAGE)
|
|
104
|
+
|
|
105
|
+
return verdict !== issue_wip.HELD
|
|
106
|
+
}
|
|
107
|
+
|
|
71
108
|
// Set before the scout and left set for `epic:bundle`, so both read the target's backlog.
|
|
72
109
|
function point_listings_at(filing: Filing): void {
|
|
73
110
|
if (issue_file.is_same_repository(filing.target, filing.current)) return
|
|
@@ -94,12 +131,21 @@ async function is_scout_clear(filing: Filing): Promise<boolean> {
|
|
|
94
131
|
}
|
|
95
132
|
|
|
96
133
|
// `undefined` when the create call failed. `gh`'s standard error is captured into the thrown error
|
|
97
|
-
// rather than printed, so it is printed here — otherwise the filing fails with no reason given.
|
|
134
|
+
// rather than printed, so it is printed here — otherwise the filing fails with no reason given. A label
|
|
135
|
+
// the create applies is provisioned first, so it never arrives with a generated color.
|
|
98
136
|
async function create(filing: Filing): Promise<string | undefined> {
|
|
137
|
+
repository_labels.ensure_labels(filing.target)
|
|
138
|
+
const auto_ok = await issue_auto_ok.resolve(
|
|
139
|
+
filing.args.is_auto_ok_opted_out,
|
|
140
|
+
filing.target,
|
|
141
|
+
filing.current,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
console.info(issue_auto_ok.line_of(auto_ok))
|
|
99
145
|
const request = git_gh_issue_write.issue_create_request({
|
|
100
146
|
title: filing.args.title,
|
|
101
147
|
body: filing.body,
|
|
102
|
-
labels: issue_file.labels_of(filing.args, filing.body),
|
|
148
|
+
labels: issue_file.labels_of(filing.args, filing.body, auto_ok.is_applied),
|
|
103
149
|
repo: filing.target,
|
|
104
150
|
})
|
|
105
151
|
|
|
@@ -136,7 +182,7 @@ async function file(filing: Filing): Promise<number> {
|
|
|
136
182
|
return FAILURE_EXIT_CODE
|
|
137
183
|
}
|
|
138
184
|
|
|
139
|
-
if (!(await is_scout_clear(filing))) return FAILURE_EXIT_CODE
|
|
185
|
+
if (!(await is_wip_clear(filing)) || !(await is_scout_clear(filing))) return FAILURE_EXIT_CODE
|
|
140
186
|
const url = await create(filing)
|
|
141
187
|
|
|
142
188
|
if (url === undefined) return FAILURE_EXIT_CODE
|
|
@@ -4,7 +4,12 @@ import { github_issue_url } from '#scripts/gh/github-issue-url'
|
|
|
4
4
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
5
5
|
import { issue_backlinks } from './issue-backlinks'
|
|
6
6
|
import { issue_classification } from './issue-classification'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AUTO_OK_LABEL,
|
|
9
|
+
DEPTH_LABEL_ORDER,
|
|
10
|
+
FILING_ROUTE_LABELS,
|
|
11
|
+
has_label_name,
|
|
12
|
+
} from './issue-labels'
|
|
8
13
|
import { markdown_section } from './markdown-section'
|
|
9
14
|
|
|
10
15
|
// The decisions behind `josh issue:file` (joshuafolkken/kit#2808), kept apart from the command that
|
|
@@ -31,6 +36,10 @@ interface FileArguments {
|
|
|
31
36
|
labels: ReadonlyArray<string>
|
|
32
37
|
repo: string | undefined
|
|
33
38
|
distinct: ReadonlyArray<number>
|
|
39
|
+
// `--over-cap`: the run is blocked by this filing, so the WIP cap does not hold it (`issue-wip.ts`).
|
|
40
|
+
is_over_cap: boolean
|
|
41
|
+
// `--no-auto-ok`: the Issue needs a person's judgement, so `auto-ok` is not applied (`issue-auto-ok.ts`).
|
|
42
|
+
is_auto_ok_opted_out: boolean
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
const OPTIONS = {
|
|
@@ -40,6 +49,8 @@ const OPTIONS = {
|
|
|
40
49
|
label: { type: 'string', multiple: true },
|
|
41
50
|
repo: { type: 'string' },
|
|
42
51
|
distinct: { type: 'string', multiple: true },
|
|
52
|
+
'over-cap': { type: 'boolean' },
|
|
53
|
+
'no-auto-ok': { type: 'boolean' },
|
|
43
54
|
} as const
|
|
44
55
|
|
|
45
56
|
// The label a `--depth` / `--route` value names, or `undefined` when it names none — read against the
|
|
@@ -101,7 +112,15 @@ function arguments_of(values: ParsedValues, title: string | undefined): FileArgu
|
|
|
101
112
|
|
|
102
113
|
if (labelled === undefined || !has_text(title) || !has_text(body_file)) return undefined
|
|
103
114
|
|
|
104
|
-
return {
|
|
115
|
+
return {
|
|
116
|
+
title,
|
|
117
|
+
body_file,
|
|
118
|
+
...labelled,
|
|
119
|
+
labels: values.label ?? [],
|
|
120
|
+
repo: values.repo,
|
|
121
|
+
is_over_cap: values['over-cap'] === true,
|
|
122
|
+
is_auto_ok_opted_out: values['no-auto-ok'] === true,
|
|
123
|
+
}
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
// An unknown flag is `undefined` rather than a throw, so the answer is the usage line, not a stack trace.
|
|
@@ -121,12 +140,16 @@ function is_undeclared_classification(label: string, body: string): boolean {
|
|
|
121
140
|
|
|
122
141
|
// Every label the filing carries: the depth, the route when there is one, the classification labels
|
|
123
142
|
// the body declares (`issue_classification.labels_for`, which merges case-insensitively), then any
|
|
124
|
-
// extra `--label
|
|
125
|
-
|
|
143
|
+
// extra `--label`, then `auto-ok` when the filing opted in (`issue-auto-ok.ts`) and no `--label`
|
|
144
|
+
// already named it. One request applies them all, so no label is a separate step to forget.
|
|
145
|
+
function labels_of(args: FileArguments, body: string, is_auto_ok = false): ReadonlyArray<string> {
|
|
126
146
|
const extra = args.labels.filter((label) => !is_undeclared_classification(label, body))
|
|
127
147
|
const declared = [args.depth, ...(args.route === undefined ? [] : [args.route]), ...extra]
|
|
148
|
+
const labels = issue_classification.labels_for(body, declared)
|
|
128
149
|
|
|
129
|
-
|
|
150
|
+
if (!is_auto_ok || has_label_name(labels, AUTO_OK_LABEL)) return labels
|
|
151
|
+
|
|
152
|
+
return [...labels, AUTO_OK_LABEL]
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
function is_same_repository(target: string, current: string): boolean {
|
|
@@ -16,7 +16,7 @@ const PLANNED_LABEL = 'run:planned'
|
|
|
16
16
|
// forever (joshuafolkken/kit#860).
|
|
17
17
|
const NEEDS_DECISION_LABEL = 'needs-decision'
|
|
18
18
|
// Marks an issue whose defect reaches the verification path, so it runs with nothing beside it
|
|
19
|
-
// (joshuafolkken/kit#2776). A run applies it from the verification-path enumeration in `
|
|
19
|
+
// (joshuafolkken/kit#2776). A run applies it from the verification-path enumeration in `backlogrun-lanes.md`;
|
|
20
20
|
// `backlog:next` enforces it (`backlog-solo.ts`), so the rule no longer rests on a judgement at
|
|
21
21
|
// dispatch time.
|
|
22
22
|
const RUN_SOLO_LABEL = 'run:solo'
|
|
@@ -36,11 +36,12 @@ const PRIORITY_HIGH_LABEL = 'priority:high'
|
|
|
36
36
|
const AUTO_OK_LABEL = 'auto-ok'
|
|
37
37
|
// Degrades one issue's run to a `halfrun`-shaped stop: it is implemented and taken through the
|
|
38
38
|
// verification gate, and then nothing is committed, pushed, opened as a pull request or merged
|
|
39
|
-
// (joshuafolkken/kit#1125). **Only a person applies it
|
|
40
|
-
//
|
|
39
|
+
// (joshuafolkken/kit#1125). **Only a person applies it** — a mark a run could clear for itself is not
|
|
40
|
+
// a mark.
|
|
41
41
|
//
|
|
42
|
-
// It is the opposite of `auto-ok` in what it does
|
|
43
|
-
//
|
|
42
|
+
// It is the opposite of `auto-ok` in what it does: one widens unattended execution past an epic's
|
|
43
|
+
// edge, the other withholds the last step of it. Unlike `auto-ok`, which `issue:file` applies by
|
|
44
|
+
// default to work found by opted-in work (joshuafolkken/kit#3213), no run ever applies it. `auto-ok`
|
|
44
45
|
// answers "may this run at all", this one answers "may its result ship without a person looking".
|
|
45
46
|
//
|
|
46
47
|
// **Not `needs-decision`, and the difference is what the two sets below encode.** A parked issue is
|
|
@@ -154,7 +155,7 @@ const FILING_ROUTE_LABELS: ReadonlyArray<{
|
|
|
154
155
|
//
|
|
155
156
|
// **The depth is read off the subject, so a run applies it.** Unlike `auto-ok` and
|
|
156
157
|
// `needs-human-review` this is not a person's judgement about authorization; it is the same reading
|
|
157
|
-
//
|
|
158
|
+
// `observation-filing.md` already asks a run to make before it files, and none of the three withholds or widens anything
|
|
158
159
|
// a run may do.
|
|
159
160
|
const DEPTH_0_LABEL = 'depth:0'
|
|
160
161
|
const DEPTH_1_LABEL = 'depth:1'
|
|
@@ -171,9 +172,8 @@ const DEPTH_LABEL_ORDER: ReadonlyArray<string> = [DEPTH_0_LABEL, DEPTH_1_LABEL,
|
|
|
171
172
|
// description, so provisioning from here is what gives each one a stable color a reader can scan by.
|
|
172
173
|
// Green, amber and pale blue, so the consumer-facing depth is the one that stands out in a listing.
|
|
173
174
|
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
// those three command lines to this array, so a color changed here without the document fails.
|
|
175
|
+
// **`josh issue:file` provisions them** through `repository_labels.ensure_labels` before its create
|
|
176
|
+
// call (joshuafolkken/kit#3176), so no document carries a creation command to keep in step.
|
|
177
177
|
const DEPTH_LABELS: ReadonlyArray<{
|
|
178
178
|
name: string
|
|
179
179
|
color: string
|
|
@@ -182,17 +182,17 @@ const DEPTH_LABELS: ReadonlyArray<{
|
|
|
182
182
|
{
|
|
183
183
|
name: DEPTH_0_LABEL,
|
|
184
184
|
color: '0e8a16',
|
|
185
|
-
description: 'Depth 0 — what a consumer of this package touches (
|
|
185
|
+
description: 'Depth 0 — what a consumer of this package touches (observation-filing.md)',
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
name: DEPTH_1_LABEL,
|
|
189
189
|
color: 'fbc02d',
|
|
190
|
-
description: 'Depth 1 — the run orchestration that executes an Issue (
|
|
190
|
+
description: 'Depth 1 — the run orchestration that executes an Issue (observation-filing.md)',
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
name: DEPTH_2_LABEL,
|
|
194
194
|
color: 'c5def5',
|
|
195
|
-
description: 'Depth 2 — what measures a run (
|
|
195
|
+
description: 'Depth 2 — what measures a run (observation-filing.md)',
|
|
196
196
|
},
|
|
197
197
|
]
|
|
198
198
|
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
//
|
|
16
16
|
// **`run:solo` alone is not a key.** It means "runs alone", not "runs first": tidying that only touches
|
|
17
17
|
// the verification path carries it too, and a low-priority one is not owed a place ahead of the queue.
|
|
18
|
-
// Only a defect on that path is, because `
|
|
18
|
+
// Only a defect on that path is, because `backlogrun-lanes.md` holds a batch until it has merged.
|
|
19
19
|
|
|
20
20
|
interface RankRow {
|
|
21
21
|
labels: ReadonlyArray<string>
|
|
@@ -8,7 +8,7 @@ import { issue_report_failures, type ReadFailureKind } from './issue-report-fail
|
|
|
8
8
|
// `josh issue:read <N> [<N> ...]` — the body *and* the comments of every issue named, in one call
|
|
9
9
|
// (joshuafolkken/kit#1715).
|
|
10
10
|
//
|
|
11
|
-
// It replaces the two `gh api` reads `.claude/skills/workflow-commands/
|
|
11
|
+
// It replaces the two `gh api` reads `.claude/skills/workflow-commands/issue-comments.md` tells an agent
|
|
12
12
|
// to type per issue. Measured over four recorded `backlogrun` parents, `issue bookkeeping` was the
|
|
13
13
|
// largest single contributor to the parent's turn count — 110 of 414 turns, 26.6% — and one issue
|
|
14
14
|
// read at a time was its dominant shape: 19 `…/issues/<N>` calls and 5 `…/comments` calls, each its
|
|
@@ -23,7 +23,7 @@ import { issue_report_failures, type ReadFailureKind } from './issue-report-fail
|
|
|
23
23
|
// prevent.
|
|
24
24
|
//
|
|
25
25
|
// **A failed read is never printed as an issue**, and neither is a failed comment listing printed as
|
|
26
|
-
// "no comments" — the block says which of the two it holds, because
|
|
26
|
+
// "no comments" — the block says which of the two it holds, because `issue-comments.md`'s rule is that the later text
|
|
27
27
|
// wins, and a comment nobody read cannot win anything.
|
|
28
28
|
|
|
29
29
|
const FAILURE_EXIT_CODE = 1
|
|
@@ -88,7 +88,7 @@ async function read_issue(issue_number: string): Promise<NumberResult> {
|
|
|
88
88
|
|
|
89
89
|
// One issue's block for a caller that wants the text rather than the printing — `run:prep` composes it
|
|
90
90
|
// with the state block and the dependency-update line. A failed read is returned as its kind, never as
|
|
91
|
-
// an empty block, so
|
|
91
|
+
// an empty block, so `issue-comments.md`'s "a comment nobody read cannot win anything" holds through the bundle too.
|
|
92
92
|
async function read_block(issue_number: string): Promise<BlockRead> {
|
|
93
93
|
const result = await read_issue(issue_number)
|
|
94
94
|
|
|
@@ -129,6 +129,7 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
129
129
|
issue_report_failures.print_blocks(
|
|
130
130
|
reports.flatMap((report) => issue_blocks(report)),
|
|
131
131
|
BLOCK_SEPARATOR,
|
|
132
|
+
`issue:read ${issue_numbers.join(' ')}`,
|
|
132
133
|
)
|
|
133
134
|
|
|
134
135
|
return issue_report_failures.report_failures(reports, FAILURE_TERMS)
|
|
@@ -6,7 +6,7 @@ import { z } from 'zod'
|
|
|
6
6
|
// (joshuafolkken/kit#1715).
|
|
7
7
|
//
|
|
8
8
|
// **It exists because reading one issue costs a parent two turns.** `.claude/skills/workflow-commands/SKILL.md`
|
|
9
|
-
//
|
|
9
|
+
// `issue-comments.md` requires the body *and* the comments, since a decision recorded after the body was written
|
|
10
10
|
// lives only in a comment; typed by hand that is `gh api repos/{owner}/{repo}/issues/<N>` followed by
|
|
11
11
|
// `gh api repos/{owner}/{repo}/issues/<N>/comments`. Measured over four recorded `backlogrun`
|
|
12
12
|
// parents, `issue bookkeeping` was the single largest contributor to the parent's turn count — 110 of
|
|
@@ -15,7 +15,7 @@ import { z } from 'zod'
|
|
|
15
15
|
// distribution this collapses.
|
|
16
16
|
//
|
|
17
17
|
// **The comments are never silently absent.** A listing that could not be read prints a line saying
|
|
18
|
-
// so, because a block showing no comment where the read failed is exactly the misread
|
|
18
|
+
// so, because a block showing no comment where the read failed is exactly the misread `issue-comments.md` exists to
|
|
19
19
|
// prevent: the agreement in force would be taken from a body a comment had already overturned.
|
|
20
20
|
|
|
21
21
|
const ISSUE_LABEL = 'issue: '
|