dev-loops 0.6.0 → 0.7.1
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/.claude-plugin/plugin.json +1 -1
- package/.claude/agents/dev-loop.md +2 -1
- package/.claude/agents/review.md +2 -1
- package/.claude/commands/{auto.md → loop-auto.md} +1 -1
- package/.claude/commands/loop-continue.md +15 -0
- package/.claude/commands/loop-enqueue.md +22 -0
- package/.claude/commands/loop-grill.md +17 -0
- package/.claude/commands/{info.md → loop-info.md} +2 -2
- package/.claude/commands/loop-queue-status.md +24 -0
- package/.claude/commands/{start-spike.md → loop-start-spike.md} +1 -1
- package/.claude/commands/{start.md → loop-start.md} +1 -1
- package/.claude/commands/{status.md → loop-status.md} +1 -1
- package/.claude/hooks/_bash-command-classify.mjs +333 -29
- package/.claude/hooks/_hook-decisions.mjs +138 -15
- package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
- package/.claude/skills/copilot-pr-followup/SKILL.md +24 -12
- package/.claude/skills/dev-loop/SKILL.md +15 -11
- package/.claude/skills/docs/acceptance-criteria-verification.md +6 -2
- package/.claude/skills/docs/anti-patterns.md +2 -1
- package/.claude/skills/docs/artifact-authority-contract.md +22 -4
- package/.claude/skills/docs/copilot-loop-operations.md +1 -1
- package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
- package/.claude/skills/docs/issue-intake-procedure.md +1 -0
- package/.claude/skills/docs/local-planning-flow.md +1 -1
- package/.claude/skills/docs/local-planning-worked-example.md +1 -1
- package/.claude/skills/docs/merge-preconditions.md +17 -1
- package/.claude/skills/docs/plan-file-contract.md +1 -1
- package/.claude/skills/docs/public-dev-loop-contract.md +1 -1
- package/.claude/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/.claude/skills/docs/ui-e2e-scoping-step.md +32 -0
- package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
- package/.claude/skills/local-implementation/SKILL.md +25 -14
- package/.claude/skills/loop-grill/SKILL.md +163 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +95 -0
- package/README.md +9 -9
- package/agents/dev-loop.agent.md +3 -2
- package/agents/developer.agent.md +1 -1
- package/agents/docs.agent.md +1 -1
- package/agents/fixer.agent.md +1 -1
- package/agents/quality.agent.md +1 -1
- package/agents/refiner.agent.md +1 -1
- package/agents/review.agent.md +3 -2
- package/cli/index.mjs +35 -42
- package/extension/README.md +4 -4
- package/extension/checks.ts +1 -5
- package/extension/harness-types.ts +1 -0
- package/extension/index.ts +6 -0
- package/extension/pi-extension-adapter.ts +2 -0
- package/extension/presentation.ts +7 -19
- package/package.json +9 -10
- package/scripts/github/capture-review-threads.mjs +2 -9
- package/scripts/github/comment-issue.mjs +2 -9
- package/scripts/github/create-label.mjs +133 -0
- package/scripts/github/detect-checkpoint-evidence.mjs +170 -14
- package/scripts/github/detect-linked-issue-pr.mjs +22 -6
- package/scripts/github/edit-pr.mjs +259 -0
- package/scripts/github/fetch-ci-logs.mjs +2 -9
- package/scripts/github/list-issues.mjs +2 -9
- package/scripts/github/manage-sub-issues.mjs +46 -10
- package/scripts/github/offer-human-handoff.mjs +8 -5
- package/scripts/github/post-gate-findings.mjs +14 -2
- package/scripts/github/probe-ci-status.mjs +8 -2
- package/scripts/github/probe-copilot-review.mjs +21 -14
- package/scripts/github/ready-for-review.mjs +26 -8
- package/scripts/github/reconcile-draft-gate.mjs +7 -3
- package/scripts/github/reply-resolve-review-thread.mjs +16 -5
- package/scripts/github/reply-resolve-review-threads.mjs +69 -7
- package/scripts/github/request-copilot-review.mjs +8 -2
- package/scripts/github/resolve-handoff-candidates.mjs +7 -3
- package/scripts/github/resolve-tracker-local-spec.mjs +9 -3
- package/scripts/github/stage-reviewer-draft.mjs +10 -2
- package/scripts/github/tick-verified-checkboxes.mjs +202 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +31 -10
- package/scripts/github/verify-fresh-review-context.mjs +150 -31
- package/scripts/github/view-pr.mjs +150 -0
- package/scripts/github/write-gate-context.mjs +248 -61
- package/scripts/github/write-gate-findings-log.mjs +75 -1
- package/scripts/lib/jq-output.mjs +18 -0
- package/scripts/loop/_post-convergence-change.mjs +211 -0
- package/scripts/loop/_pr-runner-coordination.mjs +70 -0
- package/scripts/loop/_repo-root-resolver.mjs +47 -0
- package/scripts/loop/build-handoff-envelope.mjs +14 -4
- package/scripts/loop/check-retro-tooling.mjs +14 -3
- package/scripts/loop/checkpoint-contract.mjs +7 -4
- package/scripts/loop/cleanup-worktree.mjs +12 -3
- package/scripts/loop/conductor-monitor.mjs +12 -18
- package/scripts/loop/copilot-pr-handoff.mjs +135 -14
- package/scripts/loop/debt-remediate.mjs +24 -12
- package/scripts/loop/detect-change-scope.mjs +36 -7
- package/scripts/loop/detect-copilot-loop-state.mjs +11 -5
- package/scripts/loop/detect-copilot-session-activity.mjs +7 -3
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +7 -3
- package/scripts/loop/detect-internal-only-pr.mjs +8 -2
- package/scripts/loop/detect-issue-refinement-artifact.mjs +6 -3
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +149 -66
- package/scripts/loop/detect-reviewer-loop-state.mjs +12 -2
- package/scripts/loop/detect-tracker-first-loop-state.mjs +9 -1
- package/scripts/loop/detect-tracker-pr-state.mjs +10 -3
- package/scripts/loop/ensure-worktree.mjs +8 -3
- package/scripts/loop/info.mjs +12 -4
- package/scripts/loop/inspect-run-viewer/constants.mjs +4 -18
- package/scripts/loop/inspect-run-viewer/vendor/mermaid.min.js +3405 -0
- package/scripts/loop/inspect-run-viewer-ci-changes.mjs +18 -6
- package/scripts/loop/inspect-run-viewer.mjs +67 -4
- package/scripts/loop/inspect-run.mjs +8 -2
- package/scripts/loop/outer-loop.mjs +8 -4
- package/scripts/loop/pr-runner-coordination.mjs +2 -9
- package/scripts/loop/pre-commit-branch-guard.mjs +16 -10
- package/scripts/loop/pre-flight-gate.mjs +9 -9
- package/scripts/loop/pre-pr-ready-gate.mjs +8 -4
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +13 -4
- package/scripts/loop/provision-worktree.mjs +74 -3
- package/scripts/loop/resolve-dev-loop-startup.mjs +76 -7
- package/scripts/loop/resolve-gate-dispatch.mjs +134 -0
- package/scripts/loop/resolve-pr-conflicts.mjs +14 -7
- package/scripts/loop/run-conductor-cycle.mjs +8 -2
- package/scripts/loop/run-queue.mjs +18 -9
- package/scripts/loop/run-refinement-audit.mjs +8 -9
- package/scripts/loop/run-watch-cycle.mjs +2 -9
- package/scripts/loop/sanctioned-commands.mjs +104 -0
- package/scripts/loop/steer-loop.mjs +29 -16
- package/scripts/loop/validate-pr-body-spec.mjs +207 -0
- package/scripts/loop/watch-initial-copilot-pr.mjs +8 -3
- package/scripts/pages/build-site.mjs +59 -8
- package/scripts/pages/build-state-atlas.mjs +441 -0
- package/scripts/projects/_resolve-project.mjs +148 -0
- package/scripts/projects/add-queue-item.mjs +60 -54
- package/scripts/projects/archive-done-items.mjs +49 -84
- package/scripts/projects/ensure-queue-board.mjs +10 -36
- package/scripts/projects/list-queue-items.mjs +116 -65
- package/scripts/projects/move-queue-item.mjs +28 -49
- package/scripts/projects/reconcile-queue.mjs +253 -0
- package/scripts/projects/reorder-queue-item.mjs +41 -47
- package/scripts/projects/resolve-active-board-item.mjs +108 -46
- package/scripts/projects/sync-item-status.mjs +15 -10
- package/scripts/refine/_refine-helpers.mjs +21 -5
- package/scripts/refine/exit-spike.mjs +18 -8
- package/scripts/refine/promote-plan.mjs +22 -16
- package/scripts/refine/prose-linkage-detector.mjs +1 -1
- package/scripts/refine/refine-plan-file.mjs +13 -4
- package/scripts/refine/refinement-completeness-checker.mjs +1 -1
- package/scripts/refine/scaffold-spike-file.mjs +4 -8
- package/scripts/refine/scope-boundary-cross-checker.mjs +1 -1
- package/scripts/refine/tree-integrity-validator.mjs +1 -1
- package/scripts/refine/validate-plan-file.mjs +1 -1
- package/scripts/refine/validate-spike-file.mjs +1 -1
- package/scripts/refine/verify.mjs +11 -6
- package/scripts/release/assert-core-dependency-version.mjs +123 -0
- package/scripts/release/extract-changelog-section.mjs +16 -2
- package/skills/copilot-pr-followup/SKILL.md +24 -12
- package/skills/dev-loop/SKILL.md +10 -6
- package/skills/docs/acceptance-criteria-verification.md +6 -2
- package/skills/docs/anti-patterns.md +2 -1
- package/skills/docs/artifact-authority-contract.md +22 -4
- package/skills/docs/copilot-loop-operations.md +1 -1
- package/skills/docs/cross-harness-regression-contract.md +60 -0
- package/skills/docs/issue-intake-procedure.md +1 -0
- package/skills/docs/local-planning-flow.md +1 -1
- package/skills/docs/local-planning-worked-example.md +1 -1
- package/skills/docs/merge-preconditions.md +17 -1
- package/skills/docs/plan-file-contract.md +1 -1
- package/skills/docs/public-dev-loop-contract.md +1 -1
- package/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/skills/docs/ui-e2e-scoping-step.md +32 -0
- package/skills/docs/workflow-handoff-contract.md +39 -0
- package/skills/local-implementation/SKILL.md +25 -14
- package/skills/loop-grill/SKILL.md +165 -0
- package/.claude/commands/continue.md +0 -15
- package/scripts/loop/conductor.mjs +0 -233
- package/scripts/loop/detect-stale-runner.mjs +0 -265
- package/scripts/loop/pre-push-main-guard.mjs +0 -117
|
@@ -17,6 +17,35 @@ template.
|
|
|
17
17
|
| Gate state (detectors) + strategy defaults | `currentGate`, `worktreeRequired` |
|
|
18
18
|
| Settings (`.devloops` at repo root + `defaults.yaml`) | `gateConfig`, `stopRules`, `asyncStartMode`, `requireDraftFirst`, `maxCopilotRounds` |
|
|
19
19
|
| Gate state (detectors) | `currentHeadSha`, `ciStatus`, `unresolvedThreadCount`, `copilotRoundCount` |
|
|
20
|
+
| Canonical sanctioned-command map (`scripts/loop/sanctioned-commands.mjs`) | `sanctionedCommands` |
|
|
21
|
+
|
|
22
|
+
## Sanctioned commands (MANDATORY DEFAULT — issue #1081)
|
|
23
|
+
|
|
24
|
+
`sanctionedCommands` is a **mandatory default** element of every handoff
|
|
25
|
+
envelope. It is the operation → wrapper command map (which wrapper performs
|
|
26
|
+
which GitHub/loop operation), plus the forbidden and orchestrator-owned lists.
|
|
27
|
+
The `loop build-envelope` CLI injects it into every emitted envelope, so a
|
|
28
|
+
spawned dev-loop subagent receives it verbatim without the briefer adding it —
|
|
29
|
+
no re-deriving which wrapper does `gh pr ready` etc.
|
|
30
|
+
|
|
31
|
+
The **single source of truth** is `scripts/loop/sanctioned-commands.mjs` (a
|
|
32
|
+
frozen data module). `@dev-loops/core` stays consumer-agnostic: it defines the
|
|
33
|
+
envelope SHAPE and carries whatever `sanctionedCommands` object the consumer
|
|
34
|
+
supplies; the repo-specific `scripts/...` paths live only in the consumer
|
|
35
|
+
module. Do not duplicate the map into prose — reference the module.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
sanctionedCommands?: {
|
|
39
|
+
reads: Record<string, string>; // operation → wrapper path (some also accept `loop info`)
|
|
40
|
+
edits: Record<string, string>;
|
|
41
|
+
lifecycle: Record<string, string>;
|
|
42
|
+
forbidden: string[]; // raw `gh pr view/checks/edit`, `node -e`, `python -c`, transcript tailing, sleep-poll loops
|
|
43
|
+
orchestratorOwned: string[]; // `gh pr merge`, board status transitions — never done by a subagent
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A contract test (`test/contracts/sanctioned-commands-exist.test.mjs`) asserts
|
|
48
|
+
every mapped wrapper exists on disk and fails closed if one is renamed/removed.
|
|
20
49
|
|
|
21
50
|
## Acceptance templates
|
|
22
51
|
|
|
@@ -111,6 +140,15 @@ interface HandoffEnvelope {
|
|
|
111
140
|
scopeConstraint?: string;
|
|
112
141
|
customStopAt?: string;
|
|
113
142
|
};
|
|
143
|
+
|
|
144
|
+
// Mandatory default (issue #1081). Source: scripts/loop/sanctioned-commands.mjs
|
|
145
|
+
sanctionedCommands?: {
|
|
146
|
+
reads: Record<string, string>;
|
|
147
|
+
edits: Record<string, string>;
|
|
148
|
+
lifecycle: Record<string, string>;
|
|
149
|
+
forbidden: string[];
|
|
150
|
+
orchestratorOwned: string[];
|
|
151
|
+
};
|
|
114
152
|
}
|
|
115
153
|
```
|
|
116
154
|
|
|
@@ -121,6 +159,7 @@ interface HandoffEnvelope {
|
|
|
121
159
|
3. Execute `nextAction`.
|
|
122
160
|
4. Respect `stopRules` — do not proceed past a gated stop point without authorization.
|
|
123
161
|
5. Use `acceptance` to self-validate before declaring completion.
|
|
162
|
+
6. Use `sanctionedCommands` as the authoritative operation → wrapper map: never call a raw `gh`/`node -e`/`python -c` for an operation the map covers, and never perform an `orchestratorOwned` action.
|
|
124
163
|
|
|
125
164
|
## Backward compatibility
|
|
126
165
|
|
|
@@ -43,11 +43,17 @@ Treat missing optional files as normal bootstrap conditions, not errors.
|
|
|
43
43
|
|
|
44
44
|
### Tracker-backed local implementation
|
|
45
45
|
|
|
46
|
-
Local implementation supports two durable spec
|
|
46
|
+
Local implementation supports these spec-of-record inputs. The first two are durable/committed artifacts; the third is a spec-of-record that is NOT a durable committed artifact:
|
|
47
|
+
|
|
48
|
+
Durable/committed spec artifacts:
|
|
47
49
|
|
|
48
50
|
- phase-doc-backed local sessions ([Phase Plan](../../docs/phases/phase-x.md) is canonical)
|
|
49
51
|
- tracker-backed local sessions (the tracker issue is canonical)
|
|
50
52
|
|
|
53
|
+
Non-durable spec-of-record (no committed plan artifact):
|
|
54
|
+
|
|
55
|
+
- lightweight PR-body-as-spec sessions (`--lightweight`; the PR description is the canonical spec-of-record but NOT a durable committed artifact — the resolver output carries `canonicalSpecSource: pr_body` and the derived handoff envelope carries `specSource: pr_body`; no phase/plan doc minted or committed). Same gate sequence as the phase-doc path; only the backing artifact differs. See [Artifact Authority Contract](../docs/artifact-authority-contract.md) "Lightweight (PR-body-as-spec)".
|
|
56
|
+
|
|
51
57
|
Tracker-backed local implementation stays inside the existing `local_implementation` path. For sub-issue tree decomposition, see [Sub-Issue Tree Contract](../../docs/sub-issue-tree-contract.md) (this is a source-repo reference; it is not part of the bundled `../docs/` runtime contract surface for installed skill copies). It does not introduce a new routing mode.
|
|
52
58
|
|
|
53
59
|
When the local spec already lives in a tracker issue:
|
|
@@ -130,33 +136,33 @@ Follow [Anti-patterns](../docs/anti-patterns.md) for the general tooling-interna
|
|
|
130
136
|
|
|
131
137
|
Apply [Structural Quality](../docs/structural-quality.md) from the `deep` review angle.
|
|
132
138
|
|
|
133
|
-
## Light mode (small changes)
|
|
139
|
+
## Light mode (small changes)
|
|
140
|
+
|
|
141
|
+
Light mode is wired into gate dispatch. A PR that is **under threshold** (≤ `maxLines` lines changed AND ≤ `maxFiles` files, per `.devloops` field `localImplementation.lightMode` — this repo sets `maxLines: 20`, `maxFiles: 2`; the shipped built-in default has light mode disabled (`enabled: false`); if enabled without explicit values it falls back to `maxFiles: 3` / `maxLines: 200`) AND carries no `gate:full` label collapses BOTH `draft_gate` and `pre_approval_gate` to a single inline single-agent correctness + no-op check. The Copilot review request and its polling are skipped.
|
|
134
142
|
|
|
135
|
-
|
|
143
|
+
**Escalation:**
|
|
144
|
+
1. Auto-escalate — if the inline check surfaces any finding whose severity is in the gate's `blockCleanOnFindingSeverities` (i.e. ≥ `worth-fixing-now`), dispatch escalates to full fan-out.
|
|
145
|
+
2. Label override — the `gate:full` label forces full fan-out regardless of PR size.
|
|
136
146
|
|
|
137
|
-
|
|
147
|
+
The `draft_gate` boundary (draft → ready) is still recorded because `requireDraftFirst` is honored: light mode only changes HOW the gate runs (inline vs fan-out), not WHETHER the draft boundary exists.
|
|
138
148
|
|
|
139
|
-
|
|
149
|
+
Dispatch is resolved deterministically via `resolveGateDispatchMode(config, gate, { scope, hasFullLabel, inlineFindingSeverities })` from `@dev-loops/core/config`. `scripts/loop/resolve-gate-dispatch.mjs` is the CLI wrapper the orchestrator calls, combining `detect-change-scope.mjs` output with the `gate:full` label fact.
|
|
150
|
+
|
|
151
|
+
Use `scripts/loop/detect-change-scope.mjs` to determine scope:
|
|
140
152
|
```sh
|
|
141
153
|
node scripts/loop/detect-change-scope.mjs
|
|
142
154
|
```
|
|
143
155
|
|
|
144
|
-
**Planned light mode path (not yet wired):**
|
|
145
|
-
1. Validation (`npm run verify`)
|
|
146
|
-
2. Single review pass (not multi-angle fan-out)
|
|
147
|
-
3. Pre-approval gate
|
|
148
|
-
4. Finalization
|
|
149
|
-
|
|
150
156
|
**Override threshold:**
|
|
151
157
|
```yaml
|
|
152
158
|
localImplementation:
|
|
153
159
|
lightMode:
|
|
154
160
|
enabled: true
|
|
155
|
-
maxFiles:
|
|
156
|
-
maxLines:
|
|
161
|
+
maxFiles: 2
|
|
162
|
+
maxLines: 20
|
|
157
163
|
```
|
|
158
164
|
|
|
159
|
-
|
|
165
|
+
Scope above threshold falls back to the full fan-out/fan-in path.
|
|
160
166
|
|
|
161
167
|
## Deterministic logging structure
|
|
162
168
|
|
|
@@ -264,6 +270,10 @@ For the **current phase only**, run this loop before implementation.
|
|
|
264
270
|
|
|
265
271
|
### 1. Create or update the durable phase doc and tmp scaffold
|
|
266
272
|
|
|
273
|
+
**Lightweight (PR-body-as-spec) exception:** when the session is lightweight — the resolver output carries `canonicalSpecSource: pr_body` and the derived handoff envelope carries `specSource: pr_body` (started via `resolve-dev-loop-startup.mjs --issue <n> --lightweight`) — SKIP the durable phase-doc mint entirely. The PR description is the spec-of-record; do NOT create or commit any `docs/phases/*.md` for this session. The ephemeral `tmp/phases/` scaffold may still be used for local execution state. All other steps (read prior learning, plan, implement) proceed unchanged, and the gate sequence (draft → pre-approval fanout → detect-evidence → human merge) is identical. See [Artifact Authority Contract](../docs/artifact-authority-contract.md) "Lightweight (PR-body-as-spec)".
|
|
274
|
+
|
|
275
|
+
Otherwise (default phase-doc path), create or update the durable phase doc.
|
|
276
|
+
|
|
267
277
|
Use paths like:
|
|
268
278
|
- `docs/phases/phase-0.md`
|
|
269
279
|
- `tmp/phases/phase-0/`
|
|
@@ -643,6 +653,7 @@ See [Stop Conditions](../docs/stop-conditions.md). Local-specific stops: phase c
|
|
|
643
653
|
- PR creation always goes through `dev-loops pr create`, which is ALWAYS draft and always assigned — self-assigned by default (`--assignee @me` when none is given; honors an explicit `--assignee <login>` / `-a <login>`), never via raw `gh pr create`, and the PR body must contain `Closes #N` (or `Fixes #N`) for the linked issue so GitHub auto-closes it on merge. When `.devloops` sets `workflow.requireDraftFirst` to true, use `dev-loops pr create --assignee @me ...`. Do not create a fresh PR directly in ready-for-review state unless the user explicitly overrides that policy for the current PR scope. The draft gate inspection is a real workflow boundary, so a new PR must exist in draft before `gh pr ready` is eligible.
|
|
644
654
|
- When authorization is pending, record the phase as `awaiting-finalization` and describe the exact missing step.
|
|
645
655
|
- For phase-doc-backed sessions, merge the fully reviewed, locally validated branch back into local `main` when authorized.
|
|
656
|
+
- Behind-branch integration before merge (for example a sibling PR merged first and both touch shared files): prefer a merge commit (`git merge origin/main`) over rebase + force-push. Since dev-loop PRs are squash-merged, intermediate branch history is discarded at merge time, so a merge commit lands an identical result on `main` while avoiding a non-fast-forward push to the remote. Force-push (`--force-with-lease` only, never bare `--force`) remains acceptable only for a branch the loop solely owns and where no integration alternative exists (rare); document that carve-out rather than leaving it implicit. After any integration that changes the head SHA, re-verify gate evidence at the new head (existing behavior — see [Merge Preconditions](../docs/merge-preconditions.md#required-before-merge)).
|
|
646
657
|
|
|
647
658
|
## Commit policy
|
|
648
659
|
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop-grill
|
|
3
|
+
description: >-
|
|
4
|
+
Standalone pre-loop Socratic Q&A grill for issues (tracker-first) or local
|
|
5
|
+
plan files (local-planning). Detects spec gaps, asks clarifying questions
|
|
6
|
+
(interactive) or self-answers them from codebase context (--auto), then
|
|
7
|
+
writes a ## Grill findings section back to the source artifact.
|
|
8
|
+
allowed-tools: read bash edit write
|
|
9
|
+
user-invocable: false
|
|
10
|
+
---
|
|
11
|
+
# Loop-grill skill
|
|
12
|
+
|
|
13
|
+
A standalone, on-demand pre-loop grilling skill. Run it against an issue or a local plan file **before** the dev loop starts to surface underspecified acceptance criteria, fuzzy scope boundaries, unresolved primary actors, and undocumented hard-to-reverse decisions.
|
|
14
|
+
|
|
15
|
+
It is entirely separate from the in-loop docs-grill (`docs/docs-grill-step.md`), which audits code/doc drift while the loop runs. This skill operates on the *spec* before any implementation begins.
|
|
16
|
+
|
|
17
|
+
## Interface
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
/loop-grill <issue-number> # tracker-first, interactive
|
|
21
|
+
/loop-grill <issue-number> --auto # tracker-first, auto-answer
|
|
22
|
+
/loop-grill <path/to/plan.md> # local-planning, interactive
|
|
23
|
+
/loop-grill <path/to/plan.md> --auto # local-planning, auto-answer
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Argument validation (fail-closed)
|
|
27
|
+
|
|
28
|
+
Before doing anything else:
|
|
29
|
+
|
|
30
|
+
1. Confirm exactly one positional argument is present (issue number or path). No argument → error, stop.
|
|
31
|
+
2. Confirm the only optional flag is `--auto`. Any other flag → error, stop.
|
|
32
|
+
3. **Tracker-first:** verify the issue exists. Non-existent issue → `Error: issue #<n> not found.`, stop.
|
|
33
|
+
4. **Local-planning:** verify the file exists. Missing file → `Error: plan file not found: <path>`, stop.
|
|
34
|
+
5. Never mutate any artifact when argument validation fails.
|
|
35
|
+
|
|
36
|
+
## Step 1 — Load the target
|
|
37
|
+
|
|
38
|
+
- **Tracker-first:** fetch the issue body (title + description + any existing `## Grill findings` section).
|
|
39
|
+
- **Local-planning:** read the plan file from disk.
|
|
40
|
+
|
|
41
|
+
## Step 1b — Surface external resources
|
|
42
|
+
|
|
43
|
+
Before detecting gaps, scan the loaded content for external resource references: links, other repo URLs, API endpoints, doc URLs, screenshots, or Playwright navigation descriptors.
|
|
44
|
+
|
|
45
|
+
- **Interactive mode:** if external resources are present, ask the operator to confirm they are accessible or to provide them before the Q&A starts.
|
|
46
|
+
- **`--auto` mode:** attempt to fetch each resource using bounded wrapper commands (e.g. `gh`, API wrapper scripts under `scripts/`). Do not fetch resources inline with raw `curl` or token-heavy calls. Flag any inaccessible resource as `unresolved` in the findings rather than silently skipping it.
|
|
47
|
+
- When no external resources are present, skip this step silently.
|
|
48
|
+
|
|
49
|
+
## Step 2 — Detect gaps
|
|
50
|
+
|
|
51
|
+
Scan the loaded content and identify each gap. The minimum required gap detectors are:
|
|
52
|
+
|
|
53
|
+
| Gap kind | Detection signal |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Missing acceptance criteria | No `## Acceptance criteria` section, or section is empty / stub |
|
|
56
|
+
| Missing scope boundary | No explicit in-scope / out-of-scope statement or non-goals section |
|
|
57
|
+
| Unresolved primary actor | No named user, system, or role that is the main beneficiary of the feature |
|
|
58
|
+
| Undocumented hard-to-reverse decision | Destructive or irreversible operations described without a rationale or rollback note |
|
|
59
|
+
|
|
60
|
+
Additional gaps discovered through semantic reading of the spec are also recorded.
|
|
61
|
+
|
|
62
|
+
For each gap, classify it as either:
|
|
63
|
+
- **Bounded choice** — the answer is one of a small discrete set (e.g. yes/no, A/B/C).
|
|
64
|
+
- **Open-ended** — the answer requires free-form elaboration.
|
|
65
|
+
|
|
66
|
+
## Step 3 — Fill gaps
|
|
67
|
+
|
|
68
|
+
### Interactive mode (default)
|
|
69
|
+
|
|
70
|
+
For each gap, in order:
|
|
71
|
+
|
|
72
|
+
- **Bounded choice gap:** use `AskUserQuestion` with the question text and the choice options, plus an "Other / free text" option. Block until the user answers.
|
|
73
|
+
- **Open-ended gap:** present the question as a plain text turn. Block until the user answers.
|
|
74
|
+
|
|
75
|
+
Record each answer with its source: `human`.
|
|
76
|
+
|
|
77
|
+
> `AskUserQuestion` is a Claude Code–native construct. If you are running outside Claude Code, use `--auto` mode instead.
|
|
78
|
+
|
|
79
|
+
### Auto mode (`--auto`)
|
|
80
|
+
|
|
81
|
+
Answer every grilling question yourself without prompting the user. Source answers from (in priority order):
|
|
82
|
+
|
|
83
|
+
1. **`codebase`** — inspectable source files, tests, scripts, config in the repository.
|
|
84
|
+
2. **`docs`** — markdown files under `docs/`, `skills/docs/`, and adjacent contract docs.
|
|
85
|
+
3. **`context`** — `CONTEXT.md` at the repo root, if present. When absent, skip silently — do not crash, no warning required.
|
|
86
|
+
4. **`inferred`** — reasoning from the issue/plan text alone, with no external citation.
|
|
87
|
+
|
|
88
|
+
Record the evidence source for every answer. Flag a question as **`unresolved`** when:
|
|
89
|
+
- The only available source is `inferred`, **and**
|
|
90
|
+
- No codebase path, doc section, or issue/plan text can be cited as the basis for the answer.
|
|
91
|
+
|
|
92
|
+
Do not silently guess an `inferred` answer when no evidence can be cited — flag it `unresolved` instead.
|
|
93
|
+
|
|
94
|
+
## Step 4 — Write back (replace-section semantics)
|
|
95
|
+
|
|
96
|
+
Write a `## Grill findings` section back to the source artifact using **replace-section** semantics:
|
|
97
|
+
|
|
98
|
+
- **Find** the existing `## Grill findings` section: the range from the `## Grill findings` heading through the next `##`-level heading (exclusive) or end of file.
|
|
99
|
+
- **Replace** that range in place with the new section content.
|
|
100
|
+
- If no `## Grill findings` section exists, **append** it.
|
|
101
|
+
- This makes re-runs idempotent — no accumulated noise, no duplicate sections.
|
|
102
|
+
- If parsing the section boundary fails, **abort with an error** rather than silently truncating.
|
|
103
|
+
|
|
104
|
+
**Tracker-first write-back:** update the GitHub issue body using:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
gh issue edit <n> --repo <owner/repo> --body-file <tmp-path>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The `## Grill findings` section lives in the issue body, not as a comment. Do not use `comment-issue.mjs` here — that creates a comment, not a body update.
|
|
111
|
+
|
|
112
|
+
**GitHub body size guard:** issue bodies are capped at 65,536 characters. Before writing back, check whether the updated body would exceed this limit. If so, warn: `Warning: updated body would exceed GitHub's 65,536-character limit — write-back skipped. Truncate the findings or the issue body manually.` Do not silently truncate.
|
|
113
|
+
|
|
114
|
+
**Local-planning write-back:** update the plan file in place using the edit tool.
|
|
115
|
+
|
|
116
|
+
## Output artifact format
|
|
117
|
+
|
|
118
|
+
The `## Grill findings` section written to the artifact:
|
|
119
|
+
|
|
120
|
+
```markdown
|
|
121
|
+
## Grill findings
|
|
122
|
+
|
|
123
|
+
<!-- loop-grill: <timestamp> mode:<interactive|auto> -->
|
|
124
|
+
|
|
125
|
+
### Resolved gaps
|
|
126
|
+
|
|
127
|
+
| # | Gap | Question | Answer | Source |
|
|
128
|
+
|---|-----|----------|--------|--------|
|
|
129
|
+
| 1 | Missing AC | <question text> | <answer text> | codebase \| docs \| context \| inferred \| human |
|
|
130
|
+
|
|
131
|
+
### Unresolved gaps
|
|
132
|
+
|
|
133
|
+
| # | Gap | Question | Reason unresolved |
|
|
134
|
+
|---|-----|----------|-------------------|
|
|
135
|
+
| 1 | Unresolved primary actor | <question text> | No citable evidence found |
|
|
136
|
+
|
|
137
|
+
### Verdict
|
|
138
|
+
|
|
139
|
+
grill-clean
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Replace `grill-clean` with `N unresolved items` when unresolved gaps remain.
|
|
143
|
+
|
|
144
|
+
## Step 5 — Emit verdict
|
|
145
|
+
|
|
146
|
+
After write-back, emit the verdict line to stdout:
|
|
147
|
+
|
|
148
|
+
- `grill-clean` when no unresolved gaps remain.
|
|
149
|
+
- `N unresolved items` (e.g. `3 unresolved items`) when gaps remain after all questions are answered.
|
|
150
|
+
|
|
151
|
+
## CONTEXT.md degradation rule
|
|
152
|
+
|
|
153
|
+
When `CONTEXT.md` is absent from the repo root, skip the context-source check silently. Do not crash. Do not emit a warning. The grill continues with the remaining sources (`codebase`, `docs`, `inferred`).
|
|
154
|
+
|
|
155
|
+
## Idempotency guarantee
|
|
156
|
+
|
|
157
|
+
Running `/loop-grill` twice on the same target must produce a single `## Grill findings` section, not two. The replace-section logic handles the already-present-section case. On the second run, if the gap set is identical to the first run, the section content is replaced with an equivalent section (same questions, same answers, updated timestamp).
|
|
158
|
+
|
|
159
|
+
## Non-goals
|
|
160
|
+
|
|
161
|
+
- Auto-triggering from `issue_intake` — this is on-demand only.
|
|
162
|
+
- Replacing or modifying the in-loop docs-grill (`docs/docs-grill-step.md`, `scripts/loop/docs-grill-contract.mjs`) — different concern, different firing surface.
|
|
163
|
+
- Full DDD `CONTEXT.md` management.
|
|
164
|
+
- Scheduling or storing grill runs — stateless and on-demand.
|
|
165
|
+
- Any CI/CD integration.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "Continue the dev loop — the current in-progress board item, or a given issue/PR."
|
|
3
|
-
argument-hint: "[issue|pr]"
|
|
4
|
-
---
|
|
5
|
-
<!-- GENERATED from commands/continue.command.md by scripts/claude/generate-claude-assets.mjs — do not edit; edit the source and regenerate. -->
|
|
6
|
-
|
|
7
|
-
Continue a dev loop. Two forms, both handed to the `dev-loop` skill — do NOT pick an internal strategy yourself.
|
|
8
|
-
|
|
9
|
-
- With an argument (`$ARGUMENTS` is an issue or PR — `123`, `#123`, or a GitHub URL): run the `dev-loop` skill with the public intent `continue dev loop on $ARGUMENTS`. Resolve that artifact's authoritative state first, then route; ignore board position.
|
|
10
|
-
|
|
11
|
-
- Bare (no `$ARGUMENTS`): pick up the single in-progress board item. Resolve the board's repo and project the same way the queue commands do, then run `node scripts/projects/resolve-active-board-item.mjs --repo <owner/name> --project <number>`.
|
|
12
|
-
- It returns `{ ok: true, target: { kind, number } }` for exactly one in-progress item → run the `dev-loop` skill with the public intent `continue dev loop on #<number>` (the resolved `target.number`), so the intent carries the concrete target instead of re-resolving board state.
|
|
13
|
-
- It returns `{ ok: false, reason }` when there are zero or more than one in-progress items → FAIL CLOSED: print the reason verbatim (it lists the items) and instruct the user to run `/continue #N` explicitly. Do NOT guess.
|
|
14
|
-
|
|
15
|
-
Resolve authoritative state before routing. All paths terminate in the dev-loop skill's `loop startup` → build-envelope → route, stopping at the human-approval checkpoint as usual. No new routing logic here.
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { runConductorCycle } from "./run-conductor-cycle.mjs";
|
|
3
|
-
import { runConductorMonitor } from "./conductor-monitor.mjs";
|
|
4
|
-
import { requireTokenValue } from "../_cli-primitives.mjs";
|
|
5
|
-
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
6
|
-
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
7
|
-
import {
|
|
8
|
-
loadDevLoopConfig,
|
|
9
|
-
resolveWorkflowConfig,
|
|
10
|
-
resolveAutonomyStopAt,
|
|
11
|
-
resolveGateConfig,
|
|
12
|
-
} from "@dev-loops/core/config";
|
|
13
|
-
import { readFileSync } from "node:fs";
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
import { parseArgs } from "node:util";
|
|
16
|
-
const USAGE = `Usage: conductor.mjs --repo <owner/name> [--auto-resume] [--cycle-only] [--monitor-only] [--require-retrospective]
|
|
17
|
-
Unified conductor entrypoint for dev-loop lifecycle orchestration.`.trim();
|
|
18
|
-
const parseError = buildParseError(USAGE);
|
|
19
|
-
function checkRetrospectiveGate(cwd, requireRetrospective) {
|
|
20
|
-
if (!requireRetrospective) return { blocked: false };
|
|
21
|
-
try {
|
|
22
|
-
const checkpointPath = path.join(cwd, ".pi", "dev-loop-retrospective-checkpoint.json");
|
|
23
|
-
const checkpointText = readFileSync(checkpointPath, "utf8");
|
|
24
|
-
const checkpoint = JSON.parse(checkpointText);
|
|
25
|
-
const state = typeof checkpoint?.state === "string" ? checkpoint.state.trim().toLowerCase() : null;
|
|
26
|
-
if (state === "none" || state === "complete" || state === "skipped") {
|
|
27
|
-
return { blocked: false };
|
|
28
|
-
}
|
|
29
|
-
if (state === "required" || state === "missing" || state === null || state === "") {
|
|
30
|
-
return {
|
|
31
|
-
blocked: true,
|
|
32
|
-
reason: state === "required" || state === "missing"
|
|
33
|
-
? `Retrospective checkpoint pending (state: ${state}). Complete the retrospective before running the conductor.`
|
|
34
|
-
: "Retrospective checkpoint file exists but has an unrecognized or empty state; cannot determine retrospective status safely.",
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
blocked: true,
|
|
39
|
-
reason: `Retrospective checkpoint has an unrecognized state: "${state}".`,
|
|
40
|
-
};
|
|
41
|
-
} catch (err) {
|
|
42
|
-
if (err?.code === "ENOENT") return { blocked: false };
|
|
43
|
-
return { blocked: true, reason: `Cannot read retrospective checkpoint: ${err.message}` };
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function parseCliArgs(argv) {
|
|
47
|
-
const options = {
|
|
48
|
-
help: false,
|
|
49
|
-
repo: undefined,
|
|
50
|
-
autoResume: false,
|
|
51
|
-
cycleOnly: false,
|
|
52
|
-
monitorOnly: false,
|
|
53
|
-
requireRetrospective: false,
|
|
54
|
-
};
|
|
55
|
-
const { tokens } = parseArgs({
|
|
56
|
-
args: [...argv],
|
|
57
|
-
options: {
|
|
58
|
-
help: { type: "boolean", short: "h" },
|
|
59
|
-
repo: { type: "string" },
|
|
60
|
-
"auto-resume": { type: "boolean" },
|
|
61
|
-
"cycle-only": { type: "boolean" },
|
|
62
|
-
"monitor-only": { type: "boolean" },
|
|
63
|
-
"require-retrospective": { type: "boolean" },
|
|
64
|
-
},
|
|
65
|
-
allowPositionals: true,
|
|
66
|
-
strict: false,
|
|
67
|
-
tokens: true,
|
|
68
|
-
});
|
|
69
|
-
for (const token of tokens) {
|
|
70
|
-
if (token.kind === "positional") {
|
|
71
|
-
throw parseError(`Unknown argument: ${token.value}`);
|
|
72
|
-
}
|
|
73
|
-
if (token.kind !== "option") {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (token.name === "help") {
|
|
77
|
-
options.help = true;
|
|
78
|
-
return options;
|
|
79
|
-
}
|
|
80
|
-
if (token.name === "repo") {
|
|
81
|
-
options.repo = requireTokenValue(token, parseError).trim();
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (token.name === "auto-resume") {
|
|
85
|
-
options.autoResume = true;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (token.name === "cycle-only") {
|
|
89
|
-
options.cycleOnly = true;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (token.name === "monitor-only") {
|
|
93
|
-
options.monitorOnly = true;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (token.name === "require-retrospective") {
|
|
97
|
-
options.requireRetrospective = true;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
101
|
-
}
|
|
102
|
-
if (options.repo === undefined) {
|
|
103
|
-
throw parseError("conductor requires --repo <owner/name>");
|
|
104
|
-
}
|
|
105
|
-
if (options.cycleOnly && options.monitorOnly) {
|
|
106
|
-
throw parseError("--cycle-only and --monitor-only are mutually exclusive");
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
parseRepoSlug(options.repo);
|
|
110
|
-
} catch (error) {
|
|
111
|
-
throw parseError(error instanceof Error ? error.message : String(error));
|
|
112
|
-
}
|
|
113
|
-
return options;
|
|
114
|
-
}
|
|
115
|
-
export async function runConductor(options, runtime = {}) {
|
|
116
|
-
const { cycleOnly = false, monitorOnly = false, autoResume = false, requireRetrospective: forceRetrospective = false } = options;
|
|
117
|
-
const cwd = runtime.repoRoot || process.cwd();
|
|
118
|
-
const loadConfig = runtime.loadConfigImpl || loadDevLoopConfig;
|
|
119
|
-
let configLoadResult;
|
|
120
|
-
let requireRetrospective = false;
|
|
121
|
-
let autonomyStopAt = ["merge"];
|
|
122
|
-
let gateConfig = { draft: { requireCi: true }, preApproval: { requireCi: true } };
|
|
123
|
-
try {
|
|
124
|
-
configLoadResult = await loadConfig({ repoRoot: cwd });
|
|
125
|
-
const hasErrors = Array.isArray(configLoadResult.errors) && configLoadResult.errors.length > 0;
|
|
126
|
-
if (hasErrors) {
|
|
127
|
-
configLoadResult = { ...configLoadResult };
|
|
128
|
-
} else {
|
|
129
|
-
const cfg = configLoadResult.config ?? {};
|
|
130
|
-
requireRetrospective = resolveWorkflowConfig(cfg, "requireRetrospective");
|
|
131
|
-
autonomyStopAt = resolveAutonomyStopAt(cfg);
|
|
132
|
-
const draftCfg = resolveGateConfig(cfg, "draft");
|
|
133
|
-
const preApprovalCfg = resolveGateConfig(cfg, "preApproval");
|
|
134
|
-
gateConfig = {
|
|
135
|
-
draft: { requireCi: draftCfg.requireCi },
|
|
136
|
-
preApproval: { requireCi: preApprovalCfg.requireCi },
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
} catch (error) {
|
|
140
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
141
|
-
configLoadResult = { config: null, warnings: [], errors: [{ path: "<config>", message: `Failed to load config: ${errorMessage}`, layer: "merged" }] };
|
|
142
|
-
}
|
|
143
|
-
const effectiveRequireRetrospective = forceRetrospective || requireRetrospective;
|
|
144
|
-
const retroGate = checkRetrospectiveGate(cwd, effectiveRequireRetrospective);
|
|
145
|
-
if (retroGate.blocked) {
|
|
146
|
-
return {
|
|
147
|
-
ok: false,
|
|
148
|
-
error: retroGate.reason,
|
|
149
|
-
repo: options.repo,
|
|
150
|
-
blockedByRetrospective: true,
|
|
151
|
-
checkedAt: new Date().toISOString(),
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
const runCycle = !monitorOnly;
|
|
155
|
-
const runMonitor = !cycleOnly;
|
|
156
|
-
const cycleResult = runCycle
|
|
157
|
-
? await runConductorCycle({ repo: options.repo, autonomyStopAt, gateConfig }, runtime).catch((error) => ({
|
|
158
|
-
ok: false,
|
|
159
|
-
error: error instanceof Error ? error.message : String(error),
|
|
160
|
-
}))
|
|
161
|
-
: null;
|
|
162
|
-
const monitorResult = runMonitor
|
|
163
|
-
? await runConductorMonitor({ repo: options.repo, autoResume }, runtime).catch((error) => ({
|
|
164
|
-
ok: false,
|
|
165
|
-
error: error instanceof Error ? error.message : String(error),
|
|
166
|
-
}))
|
|
167
|
-
: null;
|
|
168
|
-
const cycleOk = cycleResult?.ok === true;
|
|
169
|
-
const monitorOk = monitorResult?.ok === true;
|
|
170
|
-
return {
|
|
171
|
-
ok: (runCycle ? cycleOk : true) && (runMonitor ? monitorOk : true),
|
|
172
|
-
repo: options.repo,
|
|
173
|
-
checkedAt: new Date().toISOString(),
|
|
174
|
-
cycle: cycleResult ?? null,
|
|
175
|
-
monitor: monitorResult ?? null,
|
|
176
|
-
cycleOk,
|
|
177
|
-
monitorOk,
|
|
178
|
-
config: {
|
|
179
|
-
requireRetrospective: effectiveRequireRetrospective,
|
|
180
|
-
configRequireRetrospective: requireRetrospective,
|
|
181
|
-
autonomyStopAt,
|
|
182
|
-
gateConfig,
|
|
183
|
-
configErrors: configLoadResult?.errors?.length ?? 0,
|
|
184
|
-
},
|
|
185
|
-
summary: {
|
|
186
|
-
totalPrs: (cycleResult?.prCount ?? 0) || (monitorResult?.prCount ?? 0),
|
|
187
|
-
cycleActions: cycleResult?.actions?.length ?? 0,
|
|
188
|
-
needsSubagent: cycleResult?.summary?.needsSubagent ?? 0,
|
|
189
|
-
readyToMerge: cycleResult?.summary?.readyToMerge ?? 0,
|
|
190
|
-
waiting: cycleResult?.summary?.waiting ?? 0,
|
|
191
|
-
blocked: cycleResult?.summary?.blocked ?? 0,
|
|
192
|
-
done: cycleResult?.summary?.done ?? 0,
|
|
193
|
-
errors: cycleResult?.summary?.errors ?? 0,
|
|
194
|
-
queueStatus: monitorResult?.queueStatus ?? "unknown",
|
|
195
|
-
needsAttentionCount: monitorResult?.needsAttentionCount ?? 0,
|
|
196
|
-
orphanedPrCount: monitorResult?.orphanedPrCount ?? 0,
|
|
197
|
-
resumePlanCount: monitorResult?.resumePlanCount ?? 0,
|
|
198
|
-
manualAttentionCount: monitorResult?.manualAttentionCount ?? 0,
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
export async function runCli(
|
|
203
|
-
argv = process.argv.slice(2),
|
|
204
|
-
{
|
|
205
|
-
stdout = process.stdout,
|
|
206
|
-
env = process.env,
|
|
207
|
-
ghCommand = "gh",
|
|
208
|
-
cwd = process.cwd(),
|
|
209
|
-
} = {},
|
|
210
|
-
) {
|
|
211
|
-
const options = parseCliArgs(argv);
|
|
212
|
-
if (options.help) {
|
|
213
|
-
stdout.write(`${USAGE}\n`);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
const result = await runConductor(options, {
|
|
217
|
-
env,
|
|
218
|
-
ghCommand,
|
|
219
|
-
repoRoot: cwd,
|
|
220
|
-
});
|
|
221
|
-
if (result.ok === false) {
|
|
222
|
-
process.stderr.write(`${JSON.stringify(result)}\n`);
|
|
223
|
-
process.exitCode = 1;
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
stdout.write(`${JSON.stringify(result)}\n`);
|
|
227
|
-
}
|
|
228
|
-
if (isDirectCliRun(import.meta.url)) {
|
|
229
|
-
runCli().catch((error) => {
|
|
230
|
-
process.stderr.write(`${formatCliError(error)}\n`);
|
|
231
|
-
process.exitCode = 1;
|
|
232
|
-
});
|
|
233
|
-
}
|