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
package/cli/index.mjs
CHANGED
|
@@ -17,6 +17,34 @@ import { createPiAdapter } from "@dev-loops/core/harness";
|
|
|
17
17
|
|
|
18
18
|
const REPO_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
19
19
|
|
|
20
|
+
// `project` is an alias for `queue` minus the run driver — derived, not duplicated.
|
|
21
|
+
const QUEUE_ROUTES = {
|
|
22
|
+
run: "scripts/loop/run-queue.mjs",
|
|
23
|
+
list: "scripts/projects/list-queue-items.mjs",
|
|
24
|
+
add: "scripts/projects/add-queue-item.mjs",
|
|
25
|
+
move: "scripts/projects/move-queue-item.mjs",
|
|
26
|
+
reorder: "scripts/projects/reorder-queue-item.mjs",
|
|
27
|
+
"archive-done": "scripts/projects/archive-done-items.mjs",
|
|
28
|
+
"sync-status": "scripts/projects/sync-item-status.mjs",
|
|
29
|
+
ensure: "scripts/projects/ensure-queue-board.mjs",
|
|
30
|
+
"resolve-active": "scripts/projects/resolve-active-board-item.mjs",
|
|
31
|
+
reconcile: "scripts/projects/reconcile-queue.mjs",
|
|
32
|
+
};
|
|
33
|
+
const { run: _queueRunRoute, ...PROJECT_ROUTES } = QUEUE_ROUTES;
|
|
34
|
+
|
|
35
|
+
const QUEUE_DESCRIPTIONS = {
|
|
36
|
+
run: "Run queue driver",
|
|
37
|
+
list: "List queue board items",
|
|
38
|
+
add: "Add issue/PR to queue board",
|
|
39
|
+
move: "Move queue item between Status columns",
|
|
40
|
+
reorder: "Reorder items (move-to-top/move-after/order, --dry-run)",
|
|
41
|
+
"archive-done": "Archive closed Done items older than a duration",
|
|
42
|
+
"sync-status": "Sync a queued issue/PR's board Status column (best-effort)",
|
|
43
|
+
ensure: "Create/repair queue board bootstrap surface",
|
|
44
|
+
reconcile: "Reconcile board Status columns from live GitHub state (idempotent)",
|
|
45
|
+
};
|
|
46
|
+
const { run: _queueRunDescription, ...PROJECT_DESCRIPTIONS } = QUEUE_DESCRIPTIONS;
|
|
47
|
+
|
|
20
48
|
const SUBCOMMAND_ROUTES = {
|
|
21
49
|
gate: {
|
|
22
50
|
"upsert-verdict": "scripts/github/upsert-checkpoint-verdict.mjs",
|
|
@@ -50,27 +78,8 @@ const SUBCOMMAND_ROUTES = {
|
|
|
50
78
|
"ready-for-review": "scripts/github/ready-for-review.mjs",
|
|
51
79
|
"reconcile-draft": "scripts/github/reconcile-draft-gate.mjs",
|
|
52
80
|
},
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
add: "scripts/projects/add-queue-item.mjs",
|
|
56
|
-
move: "scripts/projects/move-queue-item.mjs",
|
|
57
|
-
reorder: "scripts/projects/reorder-queue-item.mjs",
|
|
58
|
-
"archive-done": "scripts/projects/archive-done-items.mjs",
|
|
59
|
-
"sync-status": "scripts/projects/sync-item-status.mjs",
|
|
60
|
-
ensure: "scripts/projects/ensure-queue-board.mjs",
|
|
61
|
-
"resolve-active": "scripts/projects/resolve-active-board-item.mjs",
|
|
62
|
-
},
|
|
63
|
-
queue: {
|
|
64
|
-
run: "scripts/loop/run-queue.mjs",
|
|
65
|
-
list: "scripts/projects/list-queue-items.mjs",
|
|
66
|
-
add: "scripts/projects/add-queue-item.mjs",
|
|
67
|
-
move: "scripts/projects/move-queue-item.mjs",
|
|
68
|
-
reorder: "scripts/projects/reorder-queue-item.mjs",
|
|
69
|
-
"archive-done": "scripts/projects/archive-done-items.mjs",
|
|
70
|
-
"sync-status": "scripts/projects/sync-item-status.mjs",
|
|
71
|
-
ensure: "scripts/projects/ensure-queue-board.mjs",
|
|
72
|
-
"resolve-active": "scripts/projects/resolve-active-board-item.mjs",
|
|
73
|
-
},
|
|
81
|
+
queue: QUEUE_ROUTES,
|
|
82
|
+
project: PROJECT_ROUTES,
|
|
74
83
|
inspect: {
|
|
75
84
|
run: "scripts/loop/inspect-run.mjs",
|
|
76
85
|
viewer: "scripts/loop/inspect-run-viewer.mjs",
|
|
@@ -143,25 +152,8 @@ const SUBCOMMAND_DESCRIPTIONS = {
|
|
|
143
152
|
"ready-for-review": "Mark PR ready for review",
|
|
144
153
|
"reconcile-draft": "Reconcile non-draft PR",
|
|
145
154
|
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
add: "Add issue/PR to queue board",
|
|
149
|
-
move: "Move queue item between Status columns",
|
|
150
|
-
reorder: "Reorder items (move-to-top/move-after/order, --dry-run)",
|
|
151
|
-
"archive-done": "Archive closed Done items older than a duration",
|
|
152
|
-
"sync-status": "Sync a queued issue/PR's board Status column (best-effort)",
|
|
153
|
-
ensure: "Create/repair queue board bootstrap surface",
|
|
154
|
-
},
|
|
155
|
-
queue: {
|
|
156
|
-
run: "Run queue driver",
|
|
157
|
-
list: "List queue board items",
|
|
158
|
-
add: "Add issue/PR to queue board",
|
|
159
|
-
move: "Move queue item between Status columns",
|
|
160
|
-
reorder: "Reorder items (move-to-top/move-after/order, --dry-run)",
|
|
161
|
-
"archive-done": "Archive closed Done items older than a duration",
|
|
162
|
-
"sync-status": "Sync a queued issue/PR's board Status column (best-effort)",
|
|
163
|
-
ensure: "Create/repair queue board bootstrap surface",
|
|
164
|
-
},
|
|
155
|
+
queue: QUEUE_DESCRIPTIONS,
|
|
156
|
+
project: PROJECT_DESCRIPTIONS,
|
|
165
157
|
inspect: {
|
|
166
158
|
run: "Inspect run state",
|
|
167
159
|
viewer: "Start inspection viewer",
|
|
@@ -252,7 +244,8 @@ function buildCliHelpLines() {
|
|
|
252
244
|
"Use `dev-loops <category> <subcommand> --help` for per-subcommand usage.",
|
|
253
245
|
"",
|
|
254
246
|
"`/dev-loops hide` remains an extension-only Pi command.",
|
|
255
|
-
"Run via `npx dev-loops
|
|
247
|
+
"Run via `npx dev-loops@<version>` pinned to your plugin/extension version (a global",
|
|
248
|
+
"`npm install -g dev-loops` can drift and is not the supported invocation path); see the",
|
|
256
249
|
"README for Pi-extension and Claude Code plugin setup.",
|
|
257
250
|
];
|
|
258
251
|
}
|
|
@@ -275,7 +268,7 @@ function orderedCliSetupSteps(checks) {
|
|
|
275
268
|
return [
|
|
276
269
|
"1. Use `/dev-loop` (Claude Code) or `/skill:dev-loop` (Pi) to start or continue a dev loop — the single public entry.",
|
|
277
270
|
"2. Run `dev-loops status` whenever you want a concise readiness snapshot.",
|
|
278
|
-
"3. Run via `npx dev-loops
|
|
271
|
+
"3. Run via `npx dev-loops@<version>` pinned to your plugin/extension version (a global `npm install -g dev-loops` can drift and is not the supported invocation path); see the README for Pi-extension and Claude Code plugin setup.",
|
|
279
272
|
];
|
|
280
273
|
}
|
|
281
274
|
|
package/extension/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Installing the package exposes two thin wrappers over one shared deterministic c
|
|
|
9
9
|
|
|
10
10
|
Installing the package with `pi install git:github.com/mfittko/dev-loops` exposes the packaged skills through `package.json` `pi.skills`, and the extension syncs packaged agent files (`agents/*.agent.md`) into `~/.agents/` on `session_start`.
|
|
11
11
|
|
|
12
|
+
**Version pinning (no global-install skew).** The extension resolves both the CLI and `@dev-loops/core` from the installed, pinned package (module imports, not a global `dev-loops` binary), and updates itself via the pinned `pi update git:github.com/mfittko/dev-loops` seam above. It never depends on a separately-updated global install, so it stays in lockstep with the installed version by construction (#1036).
|
|
13
|
+
|
|
12
14
|
## Command surface
|
|
13
15
|
|
|
14
16
|
- `/dev-loops`
|
|
@@ -144,7 +146,6 @@ autonomy:
|
|
|
144
146
|
|
|
145
147
|
workflow:
|
|
146
148
|
requireRetrospective: true
|
|
147
|
-
requireRetrospectiveGate: true
|
|
148
149
|
requireDraftFirst: true
|
|
149
150
|
devModeDefault: true
|
|
150
151
|
```
|
|
@@ -178,13 +179,12 @@ The optional `workflow` family carries repo-level workflow posture without hardc
|
|
|
178
179
|
```yaml
|
|
179
180
|
workflow:
|
|
180
181
|
requireRetrospective: false
|
|
181
|
-
requireRetrospectiveGate: false
|
|
182
182
|
requireDraftFirst: false
|
|
183
183
|
devModeDefault: false
|
|
184
184
|
```
|
|
185
185
|
|
|
186
186
|
- `requireRetrospective` — when enabled by repo settings, the next qualifying GitHub-first async start/resume must honor the retrospective checkpoint gate
|
|
187
|
-
- `requireRetrospectiveGate` —
|
|
187
|
+
- `requireRetrospectiveGate` — **removed (issue #1077)**. The retrospective is now advisory: it always runs and returns findings to the conductor via the envelope's `retrospectiveFindings` field and an advisory PR comment, never blocking merge or any transition. There is no longer a merge-gate config key for the retrospective.
|
|
188
188
|
- `requireDraftFirst` — marks draft-first PR creation as required workflow policy for repos that opt in
|
|
189
189
|
- `devModeDefault` — declares that local implementation should default to formal dev mode; this is config-only for now and establishes source-of-truth config plus docs for future runtime consumers
|
|
190
190
|
|
|
@@ -222,7 +222,7 @@ Current Phase 3+ contract:
|
|
|
222
222
|
|
|
223
223
|
Root verification and test commands are intentionally explicit:
|
|
224
224
|
- `npm run verify` is the canonical root verification path (`npm test` + `npm run test:dev-loop`)
|
|
225
|
-
- `npm test` runs the current root test suite (`test:assets`, `test:extension`, `test:scripts`, and `test:
|
|
225
|
+
- `npm test` runs the current root test suite (`test:assets`, `test:extension`, `test:scripts`, `test:core`, and `test:docs`)
|
|
226
226
|
- `npm run test:extension`
|
|
227
227
|
- `npm run test:extension` currently expands to one `node --import tsx --test ...` invocation in `package.json`; prefer the script entrypoint over copying the file list into downstream docs or runbooks
|
|
228
228
|
- `npm run test:scripts`
|
package/extension/checks.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEV_LOOP_CHECK_IDS } from '../lib/dev-loops-core.mjs';
|
|
2
2
|
import { createInspectRunViewerLifecycleManager } from '../scripts/loop/inspect-run-viewer/managed-instance.mjs';
|
|
3
3
|
import type { ExtensionHarnessAdapter } from './harness-types.ts';
|
|
4
4
|
|
|
@@ -77,7 +77,3 @@ export function createExtensionCoreRuntime(
|
|
|
77
77
|
},
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
-
|
|
81
|
-
export async function collectDevLoopChecks(adapter: ExtensionHarnessAdapter): Promise<DevLoopCheck[]> {
|
|
82
|
-
return collectSharedDevLoopChecks(createExtensionCoreRuntime(adapter));
|
|
83
|
-
}
|
|
@@ -29,6 +29,7 @@ export type HarnessContext = {
|
|
|
29
29
|
cwd: string;
|
|
30
30
|
hasUI: boolean;
|
|
31
31
|
ui: HarnessUi;
|
|
32
|
+
sendUserMessage?: (message: string, options?: Record<string, unknown>) => unknown;
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
export type HarnessLifecycleEvent = 'session_start' | 'tool_result' | 'user_bash' | 'agent_end';
|
package/extension/index.ts
CHANGED
|
@@ -26,6 +26,10 @@ const STATUS_KEY = 'dev-loops';
|
|
|
26
26
|
const WIDGET_KEY = 'dev-loops.setup';
|
|
27
27
|
const PACKAGED_AGENTS_ROOT = new URL('../agents/', import.meta.url);
|
|
28
28
|
|
|
29
|
+
async function dispatchDevLoopIntent(ctx: { sendUserMessage?: (message: string) => unknown }, intent: string) {
|
|
30
|
+
await ctx.sendUserMessage?.(`/skill:dev-loop ${intent}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
export function syncPackagedAgents({
|
|
30
34
|
sourceRoot = fileURLToPath(PACKAGED_AGENTS_ROOT),
|
|
31
35
|
targetRoot = path.join(os.homedir(), '.agents'),
|
|
@@ -92,10 +96,12 @@ export default function (pi: ExtensionAPI, runtimeOverrides: ExtensionRuntimeOve
|
|
|
92
96
|
return;
|
|
93
97
|
case 'entrypoint':
|
|
94
98
|
ctx.ui.setWidget(WIDGET_KEY, buildEntrypointLines(result.action, result.intent), { placement: 'belowEditor' });
|
|
99
|
+
await dispatchDevLoopIntent(ctx, result.intent);
|
|
95
100
|
ctx.ui.notify(`dev-loops ${result.action}: ${result.intent}`, 'info');
|
|
96
101
|
return;
|
|
97
102
|
case 'start_spike':
|
|
98
103
|
ctx.ui.setWidget(WIDGET_KEY, buildEntrypointLines('start-spike', result.intent), { placement: 'belowEditor' });
|
|
104
|
+
await dispatchDevLoopIntent(ctx, result.intent);
|
|
99
105
|
ctx.ui.notify(`dev-loops start-spike: ${result.intent}`, 'info');
|
|
100
106
|
return;
|
|
101
107
|
case 'checks':
|
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
|
|
27
27
|
export function toHarnessContext(ctx: Partial<ExtensionContext> | undefined): HarnessContext {
|
|
28
28
|
const ui = ctx?.ui;
|
|
29
|
+
const sender = (ctx as { sendUserMessage?: (message: string, options?: Record<string, unknown>) => unknown } | undefined)?.sendUserMessage;
|
|
29
30
|
return {
|
|
30
31
|
cwd: (ctx?.cwd as string) ?? process.cwd(),
|
|
31
32
|
hasUI: Boolean(ctx?.hasUI),
|
|
@@ -34,6 +35,7 @@ export function toHarnessContext(ctx: Partial<ExtensionContext> | undefined): Ha
|
|
|
34
35
|
setWidget: (key, lines, options) => ui?.setWidget?.(key, lines as never, options as never),
|
|
35
36
|
setStatus: (key, text) => ui?.setStatus?.(key, text as never),
|
|
36
37
|
},
|
|
38
|
+
sendUserMessage: sender ? (message, options) => sender.call(ctx, message, options) : undefined,
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -19,7 +19,7 @@ function checkMap(checks: DevLoopCheck[]): Map<DevLoopCheckId, DevLoopCheck> {
|
|
|
19
19
|
return new Map(checks.map((check) => [check.id, check]));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
function orderedSetupSteps(checks: DevLoopCheck[]): string[] {
|
|
23
23
|
const byId = checkMap(checks);
|
|
24
24
|
const uniqueSteps = [...new Set(DEV_LOOP_CHECK_IDS.filter((id) => byId.get(id)?.ok === false).map((id) => SETUP_GUIDANCE[id]))];
|
|
25
25
|
const steps = uniqueSteps.map((step, index) => `${index + 1}. ${step}`);
|
|
@@ -47,24 +47,12 @@ export function buildEntrypointLines(action: string, intent: string): string[] {
|
|
|
47
47
|
export function buildHelpLines(): string[] {
|
|
48
48
|
return [
|
|
49
49
|
'dev-loops help',
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'- /dev-loops
|
|
56
|
-
'- /dev-loops start-spike <question>',
|
|
57
|
-
'- /dev-loops info <issue|pr>',
|
|
58
|
-
'Commands:',
|
|
59
|
-
'- /dev-loops status',
|
|
60
|
-
'- /dev-loops doctor',
|
|
61
|
-
'- /dev-loops hide',
|
|
62
|
-
'- /dev-loops inspect open [--repo <owner/name>]',
|
|
63
|
-
'- /dev-loops inspect resume [--repo <owner/name>]',
|
|
64
|
-
'- /dev-loops inspect status [--repo <owner/name>]',
|
|
65
|
-
'- /dev-loops inspect stop [--repo <owner/name>]',
|
|
66
|
-
'- /dev-loops inspect restart [--repo <owner/name>]',
|
|
67
|
-
'Use `pi install git:github.com/mfittko/dev-loops` to install skills and agents; packaged agents sync into `~/.agents/` on session start.',
|
|
50
|
+
'/skill:dev-loop <intent> starts the public loop',
|
|
51
|
+
'/dev-loops start <issue> | /dev-loops auto <issue>',
|
|
52
|
+
'/dev-loops continue [issue|pr] | /dev-loops info <issue|pr>',
|
|
53
|
+
'/dev-loops start-spike <question>',
|
|
54
|
+
'/dev-loops inspect open|resume|status|stop|restart',
|
|
55
|
+
'/dev-loops status | /dev-loops doctor | /dev-loops hide',
|
|
68
56
|
];
|
|
69
57
|
}
|
|
70
58
|
|
package/package.json
CHANGED
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"verify": "npm test && npm run test:dev-loop",
|
|
25
25
|
"test": "npm run test:assets && npm run test:extension && npm run test:scripts && npm run test:core && npm run test:docs",
|
|
26
26
|
"test:assets": "node --test --test-reporter ./test/failure-summary-reporter.mjs test/dev-loop-init-phase-smoke.test.mjs test/contracts/*.test.mjs test/workflow-handoff-contract.test.mjs",
|
|
27
|
-
"test:extension": "node --import tsx --test --test-reporter ./test/failure-summary-reporter.mjs test/extension-checks.test.mjs test/extension-post-merge-update.test.mjs test/extension-command-contract.test.mjs test/extension-package-contract.test.mjs test/extension-pi-adapter.test.mjs test/
|
|
28
|
-
"test:scripts": "node --test --test-reporter ./test/failure-summary-reporter.mjs test/github/*.test.mjs test/loop/*.test.mjs test/docs/*.test.mjs test/projects/*.test.mjs test/pages/*.test.mjs",
|
|
27
|
+
"test:extension": "node --import tsx --test --test-reporter ./test/failure-summary-reporter.mjs test/extension-checks.test.mjs test/extension-post-merge-update.test.mjs test/extension-command-contract.test.mjs test/extension-package-contract.test.mjs test/extension-pi-adapter.test.mjs test/dev-loops-core.test.mjs test/dev-loops-cli.test.mjs",
|
|
28
|
+
"test:scripts": "GIT_CONFIG_COUNT=2 GIT_CONFIG_KEY_0=core.fsync GIT_CONFIG_VALUE_0=none GIT_CONFIG_KEY_1=core.fsyncObjectFiles GIT_CONFIG_VALUE_1=false node --test --test-reporter ./test/failure-summary-reporter.mjs test/github/*.test.mjs test/loop/*.test.mjs test/docs/*.test.mjs test/projects/*.test.mjs test/pages/*.test.mjs",
|
|
29
29
|
"test:core": "node --test --test-reporter ./test/failure-summary-reporter.mjs packages/core/test/*.test.mjs",
|
|
30
30
|
"test:dev-loop": "node --test --test-reporter ./test/failure-summary-reporter.mjs skills/dev-loop/scripts/dev-mode-context.test.mjs skills/dev-loop/scripts/render-template.test.mjs skills/dev-loop/scripts/post-gate-verdict-fallback.test.mjs",
|
|
31
|
-
"test:playwright:viewer": "playwright test
|
|
32
|
-
"test:playwright:deep-dive": "playwright test
|
|
33
|
-
"test:playwright:intro-deck": "playwright test
|
|
34
|
-
"test:playwright:intro-article": "playwright test
|
|
35
|
-
"test:playwright:deep-dive-article": "playwright test
|
|
31
|
+
"test:playwright:viewer": "PW_UI_SLICE=inspect-run-viewer playwright test --project=inspect-run-viewer",
|
|
32
|
+
"test:playwright:deep-dive": "PW_UI_SLICE=deep-dive-deck playwright test --project=deep-dive-deck",
|
|
33
|
+
"test:playwright:intro-deck": "PW_UI_SLICE=intro-deck playwright test --project=intro-deck",
|
|
34
|
+
"test:playwright:intro-article": "PW_UI_SLICE=intro-article playwright test --project=intro-article",
|
|
35
|
+
"test:playwright:deep-dive-article": "PW_UI_SLICE=deep-dive-article playwright test --project=deep-dive-article",
|
|
36
36
|
"smoke:headless": "node scripts/claude/headless-info-smoke.mjs",
|
|
37
37
|
"test:docs": "node scripts/docs/validate-links.mjs && node scripts/docs/validate-no-duplicate-rules.mjs",
|
|
38
38
|
"repo-wiki": "node scripts/repo-wiki.mjs",
|
|
@@ -71,8 +71,7 @@
|
|
|
71
71
|
]
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"
|
|
75
|
-
"@dev-loops/core": "^0.6.0"
|
|
74
|
+
"@dev-loops/core": "^0.7.1"
|
|
76
75
|
},
|
|
77
76
|
"repository": {
|
|
78
77
|
"type": "git",
|
|
@@ -82,7 +81,7 @@
|
|
|
82
81
|
"url": "https://github.com/mfittko/dev-loops/issues"
|
|
83
82
|
},
|
|
84
83
|
"homepage": "https://github.com/mfittko/dev-loops#readme",
|
|
85
|
-
"version": "0.
|
|
84
|
+
"version": "0.7.1",
|
|
86
85
|
"files": [
|
|
87
86
|
"cli/",
|
|
88
87
|
"lib/",
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { parseArgs } from "node:util";
|
|
12
12
|
import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
13
13
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
14
|
-
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult } from "../lib/jq-output.mjs";
|
|
14
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
15
15
|
export const REVIEW_THREADS_QUERY = [
|
|
16
16
|
"query($owner: String!, $name: String!, $pr: Int!) {",
|
|
17
17
|
" repository(owner: $owner, name: $name) {",
|
|
@@ -103,14 +103,7 @@ export function parseCaptureCliArgs(argv) {
|
|
|
103
103
|
options.pr = parsePrNumber(requireTokenValue(token));
|
|
104
104
|
continue;
|
|
105
105
|
}
|
|
106
|
-
if (token
|
|
107
|
-
options.jq = requireTokenValue(token);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (token.name === "silent") {
|
|
111
|
-
options.silent = true;
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
106
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t))) continue;
|
|
114
107
|
throw new Error(`Unknown argument: ${token.rawName}`);
|
|
115
108
|
}
|
|
116
109
|
const hasLiveArgs = options.repo !== undefined || options.pr !== undefined;
|
|
@@ -4,7 +4,7 @@ import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helper
|
|
|
4
4
|
import { parseIssueNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
|
|
5
5
|
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
|
-
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult } from "../lib/jq-output.mjs";
|
|
7
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
8
8
|
|
|
9
9
|
const USAGE = `Usage: comment-issue.mjs --repo <owner/name> --issue <number> (--body <text> | --body-file <path>)
|
|
10
10
|
Post a comment on a GitHub issue. Thin wrapper over \`gh issue comment\` — use this
|
|
@@ -82,14 +82,7 @@ export function parseCommentIssueCliArgs(argv) {
|
|
|
82
82
|
options.bodyFile = rawPath;
|
|
83
83
|
continue;
|
|
84
84
|
}
|
|
85
|
-
if (token
|
|
86
|
-
options.jq = requireTokenValue(token, parseError);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (token.name === "silent") {
|
|
90
|
-
options.silent = true;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
85
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
93
86
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
94
87
|
}
|
|
95
88
|
if (options.repo === undefined || options.issue === undefined) {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
5
|
+
import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
|
|
6
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult } from "../lib/jq-output.mjs";
|
|
7
|
+
|
|
8
|
+
const USAGE = `Usage: create-label.mjs --repo <owner/name> --name <label> [--color <hex>] [--description <text>] [--force]
|
|
9
|
+
Create (or idempotently reuse) a GitHub label. Thin wrapper over \`gh label create\`
|
|
10
|
+
— use this instead of an agent-level raw \`gh label create\` so the loop's
|
|
11
|
+
internal-tooling record stays clean (siblings: comment-issue.mjs, edit-pr.mjs).
|
|
12
|
+
Required:
|
|
13
|
+
--repo <owner/name> Repository slug (e.g. owner/repo)
|
|
14
|
+
--name <label> Label name to create
|
|
15
|
+
Optional:
|
|
16
|
+
--color <hex> Hex color without '#' (default: d73a4a)
|
|
17
|
+
--description <text> Label description
|
|
18
|
+
--force Update the label if it already exists
|
|
19
|
+
Output (stdout, JSON):
|
|
20
|
+
{ "ok": true, "created": true, "name": "gate:full", "color": "d73a4a", "repo": "owner/repo" }
|
|
21
|
+
{ "ok": true, "created": false, "alreadyExists": true, "name": "gate:full" }
|
|
22
|
+
Error output (stderr, JSON):
|
|
23
|
+
{ "ok": false, "error": "...", "usage"?: "..." }
|
|
24
|
+
${JQ_OUTPUT_USAGE}
|
|
25
|
+
Exit codes:
|
|
26
|
+
0 Success (created or already exists)
|
|
27
|
+
1 Argument error or gh failure
|
|
28
|
+
2 Invalid --jq filter`.trim();
|
|
29
|
+
const parseError = buildParseError(USAGE);
|
|
30
|
+
|
|
31
|
+
export function parseCreateLabelCliArgs(argv) {
|
|
32
|
+
const { values } = parseArgs({
|
|
33
|
+
args: [...argv],
|
|
34
|
+
options: {
|
|
35
|
+
help: { type: "boolean", short: "h" },
|
|
36
|
+
repo: { type: "string" },
|
|
37
|
+
name: { type: "string" },
|
|
38
|
+
color: { type: "string" },
|
|
39
|
+
description: { type: "string" },
|
|
40
|
+
force: { type: "boolean" },
|
|
41
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
42
|
+
},
|
|
43
|
+
allowPositionals: false,
|
|
44
|
+
strict: false,
|
|
45
|
+
});
|
|
46
|
+
if (values.help) {
|
|
47
|
+
return { help: true };
|
|
48
|
+
}
|
|
49
|
+
const repo = typeof values.repo === "string" ? values.repo.trim() : undefined;
|
|
50
|
+
const name = typeof values.name === "string" ? values.name.trim() : undefined;
|
|
51
|
+
if (!repo || !name) {
|
|
52
|
+
throw parseError("Creating a label requires both --repo <owner/name> and --name <label>");
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
parseRepoSlug(repo);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw parseError(error instanceof Error ? error.message : String(error));
|
|
58
|
+
}
|
|
59
|
+
const color = typeof values.color === "string" && values.color.trim().length > 0 ? values.color.trim() : "d73a4a";
|
|
60
|
+
const description =
|
|
61
|
+
typeof values.description === "string" && values.description.length > 0 ? values.description : undefined;
|
|
62
|
+
return {
|
|
63
|
+
help: false,
|
|
64
|
+
repo,
|
|
65
|
+
name,
|
|
66
|
+
color,
|
|
67
|
+
description,
|
|
68
|
+
force: values.force === true,
|
|
69
|
+
jq: values.jq,
|
|
70
|
+
silent: values.silent === true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Pure: assemble the `gh label create` argv. Exported so the arg shape can be
|
|
75
|
+
// tested without spawning gh.
|
|
76
|
+
export function buildLabelArgs({ repo, name, color, description, force }) {
|
|
77
|
+
const args = ["label", "create", name, "--repo", repo, "--color", color];
|
|
78
|
+
if (description !== undefined) {
|
|
79
|
+
args.push("--description", description);
|
|
80
|
+
}
|
|
81
|
+
if (force) {
|
|
82
|
+
args.push("--force");
|
|
83
|
+
}
|
|
84
|
+
return args;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createLabel(options, { ghCommand = "gh", exec = execFileSync } = {}) {
|
|
88
|
+
const args = buildLabelArgs(options);
|
|
89
|
+
try {
|
|
90
|
+
exec(ghCommand, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
91
|
+
} catch (error) {
|
|
92
|
+
const stderr = typeof error?.stderr === "string" ? error.stderr : String(error?.stderr ?? "");
|
|
93
|
+
// Without --force, gh errors if the label exists. Treat that as idempotent
|
|
94
|
+
// success so re-runs are safe; any other failure propagates.
|
|
95
|
+
if (!options.force && /already exists/i.test(stderr)) {
|
|
96
|
+
return { ok: true, created: false, alreadyExists: true, name: options.name };
|
|
97
|
+
}
|
|
98
|
+
const detail = stderr.trim() || (error instanceof Error ? error.message : String(error));
|
|
99
|
+
throw new Error(`gh label create failed: ${detail}`);
|
|
100
|
+
}
|
|
101
|
+
return { ok: true, created: true, name: options.name, color: options.color, repo: options.repo };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function run(
|
|
105
|
+
argv = process.argv.slice(2),
|
|
106
|
+
{ stdout = process.stdout, stderr = process.stderr, ghCommand = "gh", exec = execFileSync } = {},
|
|
107
|
+
) {
|
|
108
|
+
let options;
|
|
109
|
+
try {
|
|
110
|
+
options = parseCreateLabelCliArgs(argv);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
stderr.write(`${formatCliError(error)}\n`);
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
if (options.help) {
|
|
116
|
+
stdout.write(`${USAGE}\n`);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
let result;
|
|
120
|
+
try {
|
|
121
|
+
result = createLabel(options, { ghCommand, exec });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
stderr.write(`${JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) })}\n`);
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
return emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const main = run;
|
|
130
|
+
|
|
131
|
+
if (isDirectCliRun(import.meta.url)) {
|
|
132
|
+
process.exitCode = run();
|
|
133
|
+
}
|