dev-loops 0.5.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/loop-auto.md +7 -0
- 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/loop-info.md +7 -0
- package/.claude/commands/loop-queue-status.md +24 -0
- package/.claude/commands/loop-start-spike.md +16 -0
- package/.claude/commands/loop-start.md +7 -0
- package/.claude/commands/loop-status.md +6 -0
- package/.claude/hooks/_bash-command-classify.mjs +333 -29
- package/.claude/hooks/_hook-decisions.mjs +138 -15
- package/.claude/hooks/_run-context.mjs +11 -4
- 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 +19 -10
- 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/release-runbook.md +45 -0
- package/.claude/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/.claude/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
- package/.claude/skills/local-implementation/SKILL.md +26 -15
- package/.claude/skills/loop-grill/SKILL.md +163 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +125 -0
- package/README.md +29 -10
- package/agents/dev-loop.agent.md +11 -3
- 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 -40
- package/extension/README.md +4 -4
- package/extension/checks.ts +1 -5
- package/extension/harness-types.ts +1 -0
- package/extension/index.ts +16 -1
- package/extension/pi-extension-adapter.ts +2 -0
- package/extension/presentation.ts +16 -13
- package/lib/dev-loops-core.mjs +141 -0
- package/package.json +10 -8
- package/scripts/claude/generate-claude-assets.mjs +15 -1
- package/scripts/github/capture-review-threads.mjs +2 -9
- package/scripts/github/comment-issue.mjs +174 -0
- 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 +208 -0
- package/scripts/github/list-issues.mjs +184 -0
- 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/_handoff-contract.mjs +1 -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 +179 -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 +13 -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 +255 -0
- 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 +179 -0
- 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 +125 -0
- package/skills/copilot-pr-followup/SKILL.md +24 -12
- package/skills/dev-loop/SKILL.md +22 -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/release-runbook.md +45 -0
- package/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/skills/docs/workflow-handoff-contract.md +39 -0
- package/skills/local-implementation/SKILL.md +26 -15
- package/skills/loop-grill/SKILL.md +165 -0
- 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/agents/dev-loop.agent.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "dev-loop"
|
|
3
3
|
description: "Use as the single public workflow entrypoint. Route from canonical current state to the deterministic internal strategy, preferring GitHub-first paths and only using local phase implementation when explicitly requested. Keywords: dev-loop, public entrypoint, route workflow, continue dev loop."
|
|
4
|
-
tools:
|
|
4
|
+
tools: read, search, execute, bash, agent, todo, subagent
|
|
5
5
|
argument-hint: "A dev-loop intent such as issue number/URL, PR number/URL, or a request to continue/inspect current state."
|
|
6
6
|
systemPromptMode: append
|
|
7
7
|
inheritProjectContext: true
|
|
@@ -23,10 +23,18 @@ The envelope is the primary handoff artifact — it is derived from resolver out
|
|
|
23
23
|
- `nextAction` — the bounded task to execute
|
|
24
24
|
- `stopRules` — stop boundaries that must not be crossed without authorization
|
|
25
25
|
- `acceptance` — self-validation criteria for declaring completion
|
|
26
|
+
- `sanctionedCommands` — the operation → wrapper command map (reads/edits/lifecycle), plus the forbidden and orchestrator-owned lists. Carried by DEFAULT on every build so you never re-derive which wrapper performs a GitHub/loop operation. Do NOT restate the map here — the single source of truth is `scripts/loop/sanctioned-commands.mjs`, surfaced verbatim in the envelope.
|
|
26
27
|
|
|
27
28
|
**Construction sequence:**
|
|
28
29
|
<!-- pi-only -->
|
|
29
|
-
**CLI invocation (`<dev-loops-package-root>`):** dev-loop CLI commands are invoked as `node <dev-loops-package-root>/cli/index.mjs <verb...>` using the package-local CLI rather than `npx`, so they resolve unambiguously from the installed package without a global install. Resolve `<dev-loops-package-root>`
|
|
30
|
+
**CLI invocation (`<dev-loops-package-root>`):** dev-loop CLI commands are invoked as `node <dev-loops-package-root>/cli/index.mjs <verb...>` using the package-local CLI rather than `npx`, so they resolve unambiguously from the installed package without a global install. Resolve `<dev-loops-package-root>` via the first of these **bounded** candidates whose `cli/index.mjs` exists — never assume a single fixed layout (this agent may be installed user-level at `~/.agents/`, where the old `../..` package-relative guess resolves to `~`, not the package):
|
|
31
|
+
|
|
32
|
+
1. **Node module resolution** (best-effort first try): `node -e "try{const p=require('node:path');console.log(p.resolve(p.dirname(require.resolve('dev-loops/cli/index.mjs')),'..'))}catch{process.exit(1)}"` — resolves the package root when `dev-loops` is reachable from Node's module search path (notably under `~/.pi/agent/npm`); this is cwd-dependent and commonly misses from a target-repo cwd, so the probe is wrapped in try/catch (no stack trace, exits non-zero on miss) — treat a non-zero exit as "probe missed, try the next candidate", not a hard failure.
|
|
33
|
+
2. **Pi user-agent npm root** (reliable for user-level installs): `~/.pi/agent/npm/node_modules/dev-loops`.
|
|
34
|
+
3. **Package-relative (legacy):** `../..` from this agent's own directory (the original package-local install layout).
|
|
35
|
+
4. **Global npm root:** `$(npm root -g)/dev-loops`.
|
|
36
|
+
|
|
37
|
+
NEVER fall back to `find /` or any unbounded filesystem walk to locate the CLI — it stalls and trips the needs-attention timeout. If every bounded candidate fails, stop and ask the orchestrator/operator for the dev-loops package root rather than searching. (The `dev-loop` skill resolves it analogously.)
|
|
30
38
|
<!-- /pi-only -->
|
|
31
39
|
|
|
32
40
|
1. Run the deterministic startup resolver to produce the authoritative state bundle: `node <dev-loops-package-root>/cli/index.mjs loop startup --issue <n>` for issues, or `node <dev-loops-package-root>/cli/index.mjs loop startup --pr <n>` for PRs.
|
|
@@ -64,7 +72,7 @@ If local facts, GitHub facts, and helper/state-machine output do not agree well
|
|
|
64
72
|
## Subagent delegation
|
|
65
73
|
|
|
66
74
|
<!-- pi-only -->
|
|
67
|
-
This agent
|
|
75
|
+
This agent's frontmatter `tools:` comma-token scalar includes `subagent` (single-line comma form, no brackets — see #1111) and it sets `maxSubagentDepth: 3` to allow orchestrating parallel review, chains, and staged fix passes.
|
|
68
76
|
<!-- /pi-only -->
|
|
69
77
|
|
|
70
78
|
All delegation must originate from the handoff envelope: the envelope's `nextAction`, `requiredReads`, `stopRules`, and `acceptance` define the bounded task. The envelope is passed to child subagents as their primary handoff artifact.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "developer"
|
|
3
3
|
description: "Use for direct product implementation in this repository: focused code changes, refactors, tests, bug fixes, and feature work within an already-scoped task. Keywords: implement feature, write code, refactor module, add tests, fix bug, update source."
|
|
4
|
-
tools:
|
|
4
|
+
tools: read, search, execute, bash, edit, write
|
|
5
5
|
argument-hint: "Focused implementation task, relevant files, success criteria, and required verification."
|
|
6
6
|
systemPromptMode: append
|
|
7
7
|
inheritProjectContext: true
|
package/agents/docs.agent.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "docs"
|
|
3
3
|
description: "Use for README updates, plan docs, architecture notes, agent docs, migration notes, narrow documentation changes that must stay aligned with implementation work, and documentation-correctness review for the current change. Keywords: docs, README, plans, documentation, agent docs, rollout notes, changelog-style summary, docs review."
|
|
4
|
-
tools:
|
|
4
|
+
tools: read, search, execute, bash, edit, write
|
|
5
5
|
argument-hint: "Documentation task or documentation-correctness review, affected files, source changes to reflect, and required level of detail."
|
|
6
6
|
systemPromptMode: append
|
|
7
7
|
inheritProjectContext: true
|
package/agents/fixer.agent.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "fixer"
|
|
3
3
|
description: "Use for addressing active pull request review comments and threads: inspect unresolved feedback, make the narrow fix, verify it, push the fixing commit, reply with the resolving commit, and resolve the thread. Keywords: fixer, PR comments, address review feedback, resolve review threads, push fix commit."
|
|
4
|
-
tools:
|
|
4
|
+
tools: read, search, execute, bash, edit, write
|
|
5
5
|
argument-hint: "Review-fix task, PR number or branch, target reviewer/thread/file, and required verification."
|
|
6
6
|
systemPromptMode: append
|
|
7
7
|
inheritProjectContext: true
|
package/agents/quality.agent.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "quality"
|
|
3
3
|
description: "Use for build systems, test runners, type-checking, linting, package scripts, GitHub Actions workflows, caches, release verification, and quality gates. Keywords: CI, workflow, GitHub Actions, build, test, cache, typecheck, package scripts, branch protection."
|
|
4
|
-
tools:
|
|
4
|
+
tools: read, search, execute, bash, edit, write
|
|
5
5
|
argument-hint: "Quality or CI task, relevant workflows/config files, required checks, and verification expectations."
|
|
6
6
|
systemPromptMode: append
|
|
7
7
|
inheritProjectContext: true
|
package/agents/refiner.agent.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "refiner"
|
|
3
3
|
description: "Use for refining one approved implementation phase at a time into a complete, testable plan with acceptance criteria, definition of done, risks, non-goals, unresolved questions, and RFC escalation notes. Keywords: refiner, phase refinement, acceptance criteria, definition of done, RFC escalation, merged plan."
|
|
4
|
-
tools:
|
|
4
|
+
tools: read, search, execute, bash, edit, write
|
|
5
5
|
argument-hint: "Active phase doc or rough plan, phase boundary, known constraints, and any prior planning artifacts to refine."
|
|
6
6
|
systemPromptMode: append
|
|
7
7
|
inheritProjectContext: true
|
package/agents/review.agent.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "review"
|
|
3
3
|
description: "Use for pull request review from a product and engineering perspective: check the implementation against the PR description, relevant plan, acceptance criteria, definition of done, non-goals, coding best practices, security expectations, and merge readiness. Keywords: review, PR review, acceptance criteria review, DoD review, security review, plan compliance."
|
|
4
|
-
tools:
|
|
4
|
+
tools: read, search, execute, bash, edit, write
|
|
5
5
|
argument-hint: "PR number or branch, relevant plan files, and any specific review focus areas or constraints."
|
|
6
6
|
systemPromptMode: append
|
|
7
7
|
inheritProjectContext: true
|
|
@@ -33,7 +33,8 @@ This agent has two modes. The default mode is the full-PR review described in th
|
|
|
33
33
|
|
|
34
34
|
You are in scoped angle-review mode when the invocation supplies a single review `<angle>` plus a gate-context artifact path (`tmp/gate-context/<repo-slug>/pr-<N>/<gate>-<headSha>.json`, written by `scripts/github/write-gate-context.mjs`). Review like an external code reviewer hunting real bugs, not a process auditor: your job is to find concrete defects in the changed code and its call paths, not to remark that "there is no test" or that a doc section is thin. In that mode:
|
|
35
35
|
|
|
36
|
-
- **
|
|
36
|
+
- **No worktree isolation (mandatory):** you review in the PR's actual worktree/head — the same checkout the gate-context preamble ran in — never a fresh isolated worktree. You are read-only and never mutate files, so isolation buys nothing; a fresh worktree would also be checked out from `main` (not the PR head) and would lack the gitignored, worktree-local gate-context bundle below, so you'd silently review the wrong stale tree (#1135).
|
|
37
|
+
- **Fresh-context guard (mandatory):** run `node scripts/github/verify-fresh-review-context.mjs --scope <angle> --context-path <gate-context-artifact-path>` at startup. If it reports `fresh: false` (exit 1) or any error — including a missing gate-context artifact (`gateContextPresent: false`), which is the fail-closed signal that you're not in the right worktree/head — refuse to proceed and report the failure; do not review on inherited context or without seeded context. "Fresh" here means your context is the **neutral builder artifact + your angle**, and explicitly NOT the main agent's conversation/state or a prior reviewer session's state. The injected neutral bundle is the INTENDED seed (allowed); main-agent or cross-session state bleed still fails closed.
|
|
37
38
|
- **Use the PROVIDED neutral bundle as your base; do NOT re-derive it from scratch:** the gate-context artifact carries the diff (`scope.diffPath`) and a deterministic, neutral adjacent-code bundle in `adjacentCode` (each changed file plus its 1-hop import in/out-edges, with a `stripped`/`truncated`/`missing` manifest). Start from this bundle — it is the build-once, work-deduped seed shared verbatim across all reviewers. Only widen (load more files) per-angle when the bundle genuinely lacks something your angle needs; do not re-build the whole diff/adjacent-code graph yourself.
|
|
38
39
|
- **Single-angle scope:** review ONLY the supplied angle's concern. Read the gate-context artifact for the resolved angle set, change scope (branch, head SHA, touched files), acceptance-criteria pointer, and validation posture. Do not review other angles; the fan-in pass consolidates across angles.
|
|
39
40
|
- **Read the FULL diff, not just hunks:** the gate-context artifact records `scope.diffPath` (a repo-relative path to the complete captured diff) and `scope.changedFiles` (full repo-relative paths). Read the entire diff from `scope.diffPath`; if it is `null` or missing, reconstruct it yourself with `git diff` against the change's base (e.g. `git diff origin/main...HEAD` or the branch/head SHA in `scope`). Do not review off a partial or summarized hunk list — you must see every changed line for your angle.
|
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,25 +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
|
-
},
|
|
62
|
-
queue: {
|
|
63
|
-
run: "scripts/loop/run-queue.mjs",
|
|
64
|
-
list: "scripts/projects/list-queue-items.mjs",
|
|
65
|
-
add: "scripts/projects/add-queue-item.mjs",
|
|
66
|
-
move: "scripts/projects/move-queue-item.mjs",
|
|
67
|
-
reorder: "scripts/projects/reorder-queue-item.mjs",
|
|
68
|
-
"archive-done": "scripts/projects/archive-done-items.mjs",
|
|
69
|
-
"sync-status": "scripts/projects/sync-item-status.mjs",
|
|
70
|
-
ensure: "scripts/projects/ensure-queue-board.mjs",
|
|
71
|
-
},
|
|
81
|
+
queue: QUEUE_ROUTES,
|
|
82
|
+
project: PROJECT_ROUTES,
|
|
72
83
|
inspect: {
|
|
73
84
|
run: "scripts/loop/inspect-run.mjs",
|
|
74
85
|
viewer: "scripts/loop/inspect-run-viewer.mjs",
|
|
@@ -141,25 +152,8 @@ const SUBCOMMAND_DESCRIPTIONS = {
|
|
|
141
152
|
"ready-for-review": "Mark PR ready for review",
|
|
142
153
|
"reconcile-draft": "Reconcile non-draft PR",
|
|
143
154
|
},
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
add: "Add issue/PR to queue board",
|
|
147
|
-
move: "Move queue item between Status columns",
|
|
148
|
-
reorder: "Reorder items (move-to-top/move-after/order, --dry-run)",
|
|
149
|
-
"archive-done": "Archive closed Done items older than a duration",
|
|
150
|
-
"sync-status": "Sync a queued issue/PR's board Status column (best-effort)",
|
|
151
|
-
ensure: "Create/repair queue board bootstrap surface",
|
|
152
|
-
},
|
|
153
|
-
queue: {
|
|
154
|
-
run: "Run queue driver",
|
|
155
|
-
list: "List queue board items",
|
|
156
|
-
add: "Add issue/PR to queue board",
|
|
157
|
-
move: "Move queue item between Status columns",
|
|
158
|
-
reorder: "Reorder items (move-to-top/move-after/order, --dry-run)",
|
|
159
|
-
"archive-done": "Archive closed Done items older than a duration",
|
|
160
|
-
"sync-status": "Sync a queued issue/PR's board Status column (best-effort)",
|
|
161
|
-
ensure: "Create/repair queue board bootstrap surface",
|
|
162
|
-
},
|
|
155
|
+
queue: QUEUE_DESCRIPTIONS,
|
|
156
|
+
project: PROJECT_DESCRIPTIONS,
|
|
163
157
|
inspect: {
|
|
164
158
|
run: "Inspect run state",
|
|
165
159
|
viewer: "Start inspection viewer",
|
|
@@ -250,7 +244,8 @@ function buildCliHelpLines() {
|
|
|
250
244
|
"Use `dev-loops <category> <subcommand> --help` for per-subcommand usage.",
|
|
251
245
|
"",
|
|
252
246
|
"`/dev-loops hide` remains an extension-only Pi command.",
|
|
253
|
-
"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",
|
|
254
249
|
"README for Pi-extension and Claude Code plugin setup.",
|
|
255
250
|
];
|
|
256
251
|
}
|
|
@@ -273,7 +268,7 @@ function orderedCliSetupSteps(checks) {
|
|
|
273
268
|
return [
|
|
274
269
|
"1. Use `/dev-loop` (Claude Code) or `/skill:dev-loop` (Pi) to start or continue a dev loop — the single public entry.",
|
|
275
270
|
"2. Run `dev-loops status` whenever you want a concise readiness snapshot.",
|
|
276
|
-
"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.",
|
|
277
272
|
];
|
|
278
273
|
}
|
|
279
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
|
@@ -8,6 +8,7 @@ import { createExtensionCoreRuntime } from './checks.ts';
|
|
|
8
8
|
import { createPostMergeUpdateHook } from './post-merge-update.ts';
|
|
9
9
|
import { createPiExtensionAdapter, type ExtensionAPI } from './pi-extension-adapter.ts';
|
|
10
10
|
import {
|
|
11
|
+
buildEntrypointLines,
|
|
11
12
|
buildHelpLines,
|
|
12
13
|
buildInspectLines,
|
|
13
14
|
buildInspectNotification,
|
|
@@ -25,6 +26,10 @@ const STATUS_KEY = 'dev-loops';
|
|
|
25
26
|
const WIDGET_KEY = 'dev-loops.setup';
|
|
26
27
|
const PACKAGED_AGENTS_ROOT = new URL('../agents/', import.meta.url);
|
|
27
28
|
|
|
29
|
+
async function dispatchDevLoopIntent(ctx: { sendUserMessage?: (message: string) => unknown }, intent: string) {
|
|
30
|
+
await ctx.sendUserMessage?.(`/skill:dev-loop ${intent}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
export function syncPackagedAgents({
|
|
29
34
|
sourceRoot = fileURLToPath(PACKAGED_AGENTS_ROOT),
|
|
30
35
|
targetRoot = path.join(os.homedir(), '.agents'),
|
|
@@ -72,7 +77,7 @@ export default function (pi: ExtensionAPI, runtimeOverrides: ExtensionRuntimeOve
|
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
adapter.registerCommand('dev-loops', {
|
|
75
|
-
description: '
|
|
80
|
+
description: 'Run a dev-loop entrypoint or manage readiness: /dev-loops [start <issue>|auto <issue>|continue [issue|pr]|start-spike <question>|info <issue|pr>|status|doctor|gates|hide|inspect ...]',
|
|
76
81
|
handler: async (args, ctx) => {
|
|
77
82
|
const result = await executeDevLoopsCommand({
|
|
78
83
|
input: args,
|
|
@@ -89,6 +94,16 @@ export default function (pi: ExtensionAPI, runtimeOverrides: ExtensionRuntimeOve
|
|
|
89
94
|
ctx.ui.setWidget(WIDGET_KEY, buildHelpLines(), { placement: 'belowEditor' });
|
|
90
95
|
ctx.ui.notify('dev-loops help', 'info');
|
|
91
96
|
return;
|
|
97
|
+
case 'entrypoint':
|
|
98
|
+
ctx.ui.setWidget(WIDGET_KEY, buildEntrypointLines(result.action, result.intent), { placement: 'belowEditor' });
|
|
99
|
+
await dispatchDevLoopIntent(ctx, result.intent);
|
|
100
|
+
ctx.ui.notify(`dev-loops ${result.action}: ${result.intent}`, 'info');
|
|
101
|
+
return;
|
|
102
|
+
case 'start_spike':
|
|
103
|
+
ctx.ui.setWidget(WIDGET_KEY, buildEntrypointLines('start-spike', result.intent), { placement: 'belowEditor' });
|
|
104
|
+
await dispatchDevLoopIntent(ctx, result.intent);
|
|
105
|
+
ctx.ui.notify(`dev-loops start-spike: ${result.intent}`, 'info');
|
|
106
|
+
return;
|
|
92
107
|
case 'checks':
|
|
93
108
|
ctx.ui.setWidget(WIDGET_KEY, buildWidgetLines(result.action as Extract<DevLoopsAction, 'doctor' | 'status'>, result.checks), {
|
|
94
109
|
placement: 'belowEditor',
|
|
@@ -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}`);
|
|
@@ -35,21 +35,24 @@ export function orderedSetupSteps(checks: DevLoopCheck[]): string[] {
|
|
|
35
35
|
];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export function buildEntrypointLines(action: string, intent: string): string[] {
|
|
39
|
+
return [
|
|
40
|
+
`dev-loops ${action}`,
|
|
41
|
+
`Dispatch this public intent through the dev-loop skill:`,
|
|
42
|
+
`/skill:dev-loop ${intent}`,
|
|
43
|
+
'The dev-loop skill resolves authoritative state and routes deterministically — no strategy choice needed.',
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
export function buildHelpLines(): string[] {
|
|
39
48
|
return [
|
|
40
49
|
'dev-loops help',
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'- /dev-loops hide',
|
|
47
|
-
'- /dev-loops inspect open [--repo <owner/name>]',
|
|
48
|
-
'- /dev-loops inspect resume [--repo <owner/name>]',
|
|
49
|
-
'- /dev-loops inspect status [--repo <owner/name>]',
|
|
50
|
-
'- /dev-loops inspect stop [--repo <owner/name>]',
|
|
51
|
-
'- /dev-loops inspect restart [--repo <owner/name>]',
|
|
52
|
-
'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',
|
|
53
56
|
];
|
|
54
57
|
}
|
|
55
58
|
|
package/lib/dev-loops-core.mjs
CHANGED
|
@@ -9,6 +9,19 @@ const LOCAL_READINESS_IDS = ['subagent-command', 'git-repo'];
|
|
|
9
9
|
const REMOTE_READINESS_IDS = ['gh-installed', 'gh-auth', 'subagent-command', 'git-repo'];
|
|
10
10
|
const INSPECT_ACTIONS = new Set(['open', 'resume', 'status', 'stop', 'restart']);
|
|
11
11
|
|
|
12
|
+
// Direct dev-loop entrypoints (#972): thin named wrappers over the public dev-loop contract.
|
|
13
|
+
// Each maps `<verb> <issue|pr>` to the canonical public-intent shorthand the `dev-loop` skill
|
|
14
|
+
// already accepts — no new routing/strategy logic lives here. `start`/`auto` target an issue,
|
|
15
|
+
// `continue` (#988) targets an issue OR a PR (the resolver picks the canonical artifact) and
|
|
16
|
+
// also accepts a bare form (no number) that continues the current in-progress board item;
|
|
17
|
+
// `info` is the read-only state shortcut for an issue or PR.
|
|
18
|
+
const ENTRYPOINT_VERBS = {
|
|
19
|
+
start: { target: 'issue', phrase: (n) => `start dev loop on issue ${n}` },
|
|
20
|
+
auto: { target: 'issue', phrase: (n) => `auto dev loop on issue ${n}` },
|
|
21
|
+
continue: { target: 'either', allowBare: true, phrase: (n) => (n ? `continue dev loop on ${n}` : 'continue the current dev loop') },
|
|
22
|
+
info: { target: 'either', phrase: (n) => `inspect dev loop state on ${n}` },
|
|
23
|
+
};
|
|
24
|
+
|
|
12
25
|
const UNICODE_SPACE_RE = /[\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/g;
|
|
13
26
|
|
|
14
27
|
function normalizeInput(input) {
|
|
@@ -88,6 +101,100 @@ function parseInspectCommand(tokens, { surface }) {
|
|
|
88
101
|
};
|
|
89
102
|
}
|
|
90
103
|
|
|
104
|
+
// Normalize a single entrypoint target token to a bare issue/PR number.
|
|
105
|
+
// Accepts `123`, `#123`, or a GitHub issue/PR URL (.../issues/123 or .../pull/123).
|
|
106
|
+
// Returns the numeric string, or null when it is not a recognized target.
|
|
107
|
+
function normalizeTargetNumber(raw) {
|
|
108
|
+
const token = String(raw).trim();
|
|
109
|
+
if (/^#?\d+$/.test(token)) {
|
|
110
|
+
return token.replace(/^#/, '');
|
|
111
|
+
}
|
|
112
|
+
const urlMatch = token.match(/^https?:\/\/[^\s]*\/(?:issues|pull)\/(\d+)(?:[/?#].*)?$/);
|
|
113
|
+
if (urlMatch) {
|
|
114
|
+
return urlMatch[1];
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// `start-spike` is a SIBLING of the numeric verbs (start/auto/continue/info): it
|
|
120
|
+
// takes FREE TEXT (a question) or `--file <path>`, NOT a numeric target, so it is
|
|
121
|
+
// parsed on its own path to keep the numeric-validation invariant of the other
|
|
122
|
+
// verbs intact (#988 P2). It is a thin wrapper over the shipped `--spike` intake:
|
|
123
|
+
// the inline-question form scaffolds a startable findings artifact, then both
|
|
124
|
+
// forms hand the resolved spike path to `loop startup --spike <path>`. No new
|
|
125
|
+
// spike behavior lives here — see skills/docs/spike-mode-contract.md.
|
|
126
|
+
function parseStartSpikeCommand(rest, tokens) {
|
|
127
|
+
const positional = rest.filter((t) => t !== undefined);
|
|
128
|
+
// `--file <path>`: start from a pre-authored spike artifact (no scaffolding).
|
|
129
|
+
if (positional[0] === '--file') {
|
|
130
|
+
const file = positional[1];
|
|
131
|
+
// Reject any leading `-`: the path is forwarded to `resolve-dev-loop-startup
|
|
132
|
+
// --spike <path>`, so a value like `-x` could be read as an option (option
|
|
133
|
+
// injection). Fail closed. The free-text question path below is unaffected.
|
|
134
|
+
if (typeof file !== 'string' || file.length === 0 || file.startsWith('-') || positional.length !== 2) {
|
|
135
|
+
return invalidCommand('`start-spike --file` requires exactly one `<path>`.', 'start-spike', tokens);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
kind: 'start_spike',
|
|
139
|
+
mode: 'file',
|
|
140
|
+
file,
|
|
141
|
+
question: null,
|
|
142
|
+
intent: `start a dev-loop spike from ${file}`,
|
|
143
|
+
tokens,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Inline free-text question. Joined verbatim so multi-word questions survive.
|
|
147
|
+
const question = positional.join(' ').trim();
|
|
148
|
+
if (question.length === 0) {
|
|
149
|
+
return invalidCommand('`start-spike` requires a question (or `--file <path>`).', 'start-spike', tokens);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
kind: 'start_spike',
|
|
153
|
+
mode: 'question',
|
|
154
|
+
file: null,
|
|
155
|
+
question,
|
|
156
|
+
intent: `start a dev-loop spike on the question: ${question}`,
|
|
157
|
+
tokens,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function parseEntrypointCommand(action, args, tokens) {
|
|
162
|
+
const spec = ENTRYPOINT_VERBS[action];
|
|
163
|
+
const positional = args.filter((a) => a !== undefined);
|
|
164
|
+
const targetHint = spec.target === 'pr' ? '<pr>' : spec.target === 'either' ? '<issue|pr>' : '<issue>';
|
|
165
|
+
const targetNoun = spec.target === 'pr' ? 'PR' : spec.target === 'either' ? 'issue/PR' : 'issue';
|
|
166
|
+
|
|
167
|
+
// Bare form (no target): only verbs that opt in (e.g. `continue` resumes the
|
|
168
|
+
// current in-progress board item). The command/skill does the board resolve.
|
|
169
|
+
if (positional.length === 0 && spec.allowBare) {
|
|
170
|
+
return {
|
|
171
|
+
kind: 'entrypoint',
|
|
172
|
+
action,
|
|
173
|
+
target: spec.target,
|
|
174
|
+
number: null,
|
|
175
|
+
intent: spec.phrase(null),
|
|
176
|
+
tokens,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (positional.length !== 1) {
|
|
181
|
+
const arity = spec.allowBare ? `at most one ${targetHint}` : `exactly one ${targetHint}`;
|
|
182
|
+
return invalidCommand(`\`${action}\` requires ${arity} argument.`, action, tokens);
|
|
183
|
+
}
|
|
184
|
+
const number = normalizeTargetNumber(positional[0]);
|
|
185
|
+
if (number === null) {
|
|
186
|
+
return invalidCommand(`\`${action}\` expects a numeric ${targetNoun}, got: ${positional[0]}.`, action, tokens);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
kind: 'entrypoint',
|
|
190
|
+
action,
|
|
191
|
+
target: spec.target,
|
|
192
|
+
number,
|
|
193
|
+
intent: spec.phrase(number),
|
|
194
|
+
tokens,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
91
198
|
export function parseDevLoopsCommand(input, { surface = 'extension' } = {}) {
|
|
92
199
|
const tokens = normalizeInput(input);
|
|
93
200
|
const [rawAction, rawScope, ...rest] = tokens;
|
|
@@ -98,6 +205,15 @@ export function parseDevLoopsCommand(input, { surface = 'extension' } = {}) {
|
|
|
98
205
|
return parseInspectCommand(tokens, { surface });
|
|
99
206
|
}
|
|
100
207
|
|
|
208
|
+
// `start-spike` is free-text/path, not a numeric verb — parsed on its own path.
|
|
209
|
+
if (action === 'start-spike') {
|
|
210
|
+
return parseStartSpikeCommand([rawScope, ...rest], tokens);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (action && Object.prototype.hasOwnProperty.call(ENTRYPOINT_VERBS, action)) {
|
|
214
|
+
return parseEntrypointCommand(action, [rawScope, ...rest], tokens);
|
|
215
|
+
}
|
|
216
|
+
|
|
101
217
|
switch (action) {
|
|
102
218
|
case undefined:
|
|
103
219
|
case '':
|
|
@@ -255,6 +371,31 @@ export async function executeDevLoopsCommand({ input, surface = 'extension', run
|
|
|
255
371
|
}
|
|
256
372
|
}
|
|
257
373
|
|
|
374
|
+
if (parsed.kind === 'start_spike') {
|
|
375
|
+
// Thin wrapper: surface the spike intent so the operator dispatches it through
|
|
376
|
+
// the dev-loop skill, which scaffolds (inline question) or uses the given file,
|
|
377
|
+
// then runs `loop startup --spike <path>`. No spike behavior is decided here.
|
|
378
|
+
return {
|
|
379
|
+
kind: 'start_spike',
|
|
380
|
+
mode: parsed.mode,
|
|
381
|
+
file: parsed.file,
|
|
382
|
+
question: parsed.question,
|
|
383
|
+
intent: parsed.intent,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (parsed.kind === 'entrypoint') {
|
|
388
|
+
// Thin wrapper: surface the canonical public intent so the user dispatches it through the
|
|
389
|
+
// `dev-loop` skill (the single public router). No routing/strategy decision is made here.
|
|
390
|
+
return {
|
|
391
|
+
kind: 'entrypoint',
|
|
392
|
+
action: parsed.action,
|
|
393
|
+
target: parsed.target,
|
|
394
|
+
number: parsed.number,
|
|
395
|
+
intent: parsed.intent,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
258
399
|
if (parsed.kind !== 'action') {
|
|
259
400
|
return parsed;
|
|
260
401
|
}
|
package/package.json
CHANGED
|
@@ -24,13 +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
|
|
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",
|
|
34
36
|
"smoke:headless": "node scripts/claude/headless-info-smoke.mjs",
|
|
35
37
|
"test:docs": "node scripts/docs/validate-links.mjs && node scripts/docs/validate-no-duplicate-rules.mjs",
|
|
36
38
|
"repo-wiki": "node scripts/repo-wiki.mjs",
|
|
@@ -69,8 +71,7 @@
|
|
|
69
71
|
]
|
|
70
72
|
},
|
|
71
73
|
"dependencies": {
|
|
72
|
-
"
|
|
73
|
-
"@dev-loops/core": "^0.5.0"
|
|
74
|
+
"@dev-loops/core": "^0.7.1"
|
|
74
75
|
},
|
|
75
76
|
"repository": {
|
|
76
77
|
"type": "git",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"url": "https://github.com/mfittko/dev-loops/issues"
|
|
81
82
|
},
|
|
82
83
|
"homepage": "https://github.com/mfittko/dev-loops#readme",
|
|
83
|
-
"version": "0.
|
|
84
|
+
"version": "0.7.1",
|
|
84
85
|
"files": [
|
|
85
86
|
"cli/",
|
|
86
87
|
"lib/",
|
|
@@ -91,6 +92,7 @@
|
|
|
91
92
|
".claude-plugin/",
|
|
92
93
|
".claude/.claude-plugin/",
|
|
93
94
|
".claude/agents/",
|
|
95
|
+
".claude/commands/",
|
|
94
96
|
".claude/skills/",
|
|
95
97
|
".claude/hooks/",
|
|
96
98
|
"README.md",
|