dw-kit 1.6.0-rc.1 → 1.7.0-rc.2
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/hooks/pre-commit-gate.sh +17 -19
- package/.dw/core/schemas/agent-claim.schema.json +21 -1
- package/.dw/core/schemas/goal-frontmatter.schema.json +84 -0
- package/.dw/core/schemas/task-frontmatter.schema.json +22 -3
- package/.dw/core/templates/v3/goal.md +146 -0
- package/CLAUDE.md +1 -1
- package/package.json +1 -1
- package/src/cli.mjs +182 -3
- package/src/commands/agent-check-staged.mjs +63 -0
- package/src/commands/agent-claim.mjs +165 -13
- package/src/commands/agent-inspect.mjs +4 -0
- package/src/commands/agent-verify.mjs +75 -0
- package/src/commands/goal-bump.mjs +50 -0
- package/src/commands/goal-delete.mjs +120 -0
- package/src/commands/goal-link.mjs +126 -0
- package/src/commands/goal-lint.mjs +152 -0
- package/src/commands/goal-new.mjs +86 -0
- package/src/commands/goal-portfolio.mjs +84 -0
- package/src/commands/goal-render.mjs +49 -0
- package/src/commands/goal-set.mjs +62 -0
- package/src/commands/goal-show.mjs +94 -0
- package/src/commands/goal-stubs.mjs +21 -0
- package/src/commands/goal-suggest-krs.mjs +139 -0
- package/src/commands/goal-summary.mjs +67 -0
- package/src/commands/goal-view.mjs +196 -0
- package/src/commands/task-migrate.mjs +105 -0
- package/src/commands/task-summary.mjs +68 -0
- package/src/commands/task-watch.mjs +667 -6
- package/src/lib/agent-claim.mjs +47 -0
- package/src/lib/agent-conflict.mjs +168 -21
- package/src/lib/agent-events.mjs +98 -9
- package/src/lib/goal-events.mjs +147 -0
- package/src/lib/goal-store.mjs +202 -0
- package/src/lib/goal-svg.mjs +293 -0
- package/src/lib/goal-watch.mjs +133 -0
- package/src/lib/sse-broker.mjs +91 -0
- package/src/lib/watch-auth.mjs +64 -0
|
@@ -96,27 +96,25 @@ fi
|
|
|
96
96
|
# v1.6 (ADR-0009 R2-2): Agent OS post-hoc check.
|
|
97
97
|
# When ≥1 active claim exists, verify staged files fall within an active claim's write_scope.
|
|
98
98
|
# Cooperative protocol — warn only (cannot prevent non-compliant agents).
|
|
99
|
-
|
|
99
|
+
#
|
|
100
|
+
# Issue #13 Bug 3 fix: previous version inlined `node -e` importing
|
|
101
|
+
# `$CLAUDE_PROJECT_DIR/src/lib/agent-claim.mjs` — that path is cwd-relative and
|
|
102
|
+
# only exists in dw-kit's own repo, NOT in consumer projects. The block was
|
|
103
|
+
# silently no-op everywhere else. Replaced by `dw agent check-staged --stdin`
|
|
104
|
+
# which uses the installed binary. Prefer global `dw` on PATH; fall back to
|
|
105
|
+
# `node $DW_BIN` for in-repo local development (where DW_BIN points at the
|
|
106
|
+
# source bin script).
|
|
107
|
+
if [ -n "$STAGED_FILES" ]; then
|
|
100
108
|
CLAIMS_DIR="$CLAUDE_PROJECT_DIR/.dw/cache/agents/claims"
|
|
101
109
|
if [ -d "$CLAIMS_DIR" ] && ls "$CLAIMS_DIR"/*.json >/dev/null 2>&1; then
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const allScopes = claims.flatMap(c => c.write_scope);
|
|
111
|
-
const staged = (process.env.STAGED || '').split(/\r?\n/).filter(Boolean);
|
|
112
|
-
const outside = staged.filter(f => !pathMatchesScope(f, allScopes));
|
|
113
|
-
if (outside.length > 0) {
|
|
114
|
-
console.log('Files NOT in any active claim write_scope:');
|
|
115
|
-
for (const f of outside.slice(0, 10)) console.log(' ' + f);
|
|
116
|
-
if (outside.length > 10) console.log(' ... +' + (outside.length - 10) + ' more');
|
|
117
|
-
}
|
|
118
|
-
})().catch(e => { /* fail-graceful */ });
|
|
119
|
-
" 2>/dev/null || true)
|
|
110
|
+
if command -v dw >/dev/null 2>&1; then
|
|
111
|
+
OUT_OF_SCOPE=$(echo "$STAGED_FILES" | dw agent check-staged --stdin 2>/dev/null || true)
|
|
112
|
+
elif [ -f "$DW_BIN" ] && command -v node >/dev/null 2>&1; then
|
|
113
|
+
# Local dev fallback: in-repo dw-kit work where global `dw` not on PATH
|
|
114
|
+
OUT_OF_SCOPE=$(echo "$STAGED_FILES" | node "$DW_BIN" agent check-staged --stdin 2>/dev/null || true)
|
|
115
|
+
else
|
|
116
|
+
OUT_OF_SCOPE=""
|
|
117
|
+
fi
|
|
120
118
|
if [ -n "$OUT_OF_SCOPE" ]; then
|
|
121
119
|
echo "⚠️ Agent OS (ADR-0009 R2-2): post-hoc claim check" >&2
|
|
122
120
|
echo "$OUT_OF_SCOPE" >&2
|
|
@@ -114,7 +114,27 @@
|
|
|
114
114
|
},
|
|
115
115
|
"worktree_path": {
|
|
116
116
|
"type": "string",
|
|
117
|
-
"description": "
|
|
117
|
+
"description": "Absolute path to the git worktree this agent operates in (issue #15). dw does NOT create or delete this directory — the caller harness owns lifecycle (`git worktree add` / `git worktree remove`). When two claims declare distinct worktree_path values, dw conflict detection skips write_scope comparison between them (physical isolation). One or both null → conservative; assume overlap."
|
|
118
|
+
},
|
|
119
|
+
"original_lease_expires": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
|
|
122
|
+
"description": "Issue #15: lease_expires at claim creation time (immutable). Renewals do NOT change this — caps total lease window via max_renewals."
|
|
123
|
+
},
|
|
124
|
+
"renewed_at": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
|
|
127
|
+
"description": "Issue #15: timestamp of last `dw agent renew` call. Stamped on each renewal; previous value overwritten."
|
|
128
|
+
},
|
|
129
|
+
"renewal_count": {
|
|
130
|
+
"type": "integer",
|
|
131
|
+
"minimum": 0,
|
|
132
|
+
"description": "Issue #15: number of times this claim has been renewed. Bounded by MAX_RENEWALS (default 3) to prevent unbounded lease compounding."
|
|
133
|
+
},
|
|
134
|
+
"previous_lease_expires": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
|
|
137
|
+
"description": "Issue #15: lease_expires value immediately before the most recent renewal. Provides self-describing renewal history without depending on events.jsonl."
|
|
118
138
|
}
|
|
119
139
|
},
|
|
120
140
|
"allOf": [
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/dv-workflow/dv-workflow/blob/main/.dw/core/schemas/goal-frontmatter.schema.json",
|
|
4
|
+
"title": "goal@v1",
|
|
5
|
+
"description": "Frontmatter schema for .dw/goals/{goal-id}/goal.md (ADR-0010 Goals Management Layer, v1.7).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"goal_id",
|
|
9
|
+
"schema_version",
|
|
10
|
+
"created",
|
|
11
|
+
"last_updated",
|
|
12
|
+
"status",
|
|
13
|
+
"owner",
|
|
14
|
+
"goal_version"
|
|
15
|
+
],
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"properties": {
|
|
18
|
+
"goal_id": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"pattern": "^G-[A-Za-z0-9](?:[A-Za-z0-9.-]{0,31}[A-Za-z0-9])?$",
|
|
21
|
+
"description": "Goal identifier. Format G-{slug}; uniqueness enforced via goals-index.json (W-1)."
|
|
22
|
+
},
|
|
23
|
+
"schema_version": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"const": "goal@v1",
|
|
26
|
+
"description": "Schema version. Upgrade path via `dw goal migrate --to goal@v2` (S-1)."
|
|
27
|
+
},
|
|
28
|
+
"created": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
31
|
+
"description": "ISO date YYYY-MM-DD"
|
|
32
|
+
},
|
|
33
|
+
"last_updated": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
36
|
+
"description": "ISO date YYYY-MM-DD"
|
|
37
|
+
},
|
|
38
|
+
"status": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"enum": ["Draft", "Active", "Achieved", "Abandoned", "Pivoted"],
|
|
41
|
+
"description": "Q3 OKR-style lifecycle. Status transitions auto-bump goal_version (Q4/C-1)."
|
|
42
|
+
},
|
|
43
|
+
"owner": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 1,
|
|
46
|
+
"description": "Person responsible for shipping this goal."
|
|
47
|
+
},
|
|
48
|
+
"target_date": {
|
|
49
|
+
"type": ["string", "null"],
|
|
50
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$|^TBD$",
|
|
51
|
+
"description": "ISO date YYYY-MM-DD or 'TBD' or null."
|
|
52
|
+
},
|
|
53
|
+
"goal_version": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"minimum": 1,
|
|
56
|
+
"description": "Q4/C-1: Bumped only on manual `dw goal bump --reason` or status transition (goal_status_changed event). Field edits (KR progress, summary) emit goal_field_updated event without counter increment."
|
|
57
|
+
},
|
|
58
|
+
"archived_at": {
|
|
59
|
+
"type": ["string", "null"],
|
|
60
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
|
|
61
|
+
"description": "C-2: Soft-delete timestamp ISO UTC. null = active. Set automatically when status transitions to Abandoned or via `dw goal delete`."
|
|
62
|
+
},
|
|
63
|
+
"parent_goal_id": {
|
|
64
|
+
"type": ["string", "null"],
|
|
65
|
+
"pattern": "^G-[A-Za-z0-9](?:[A-Za-z0-9.-]{0,31}[A-Za-z0-9])?$|^none$",
|
|
66
|
+
"description": "Optional parent goal (for sub-goals). 'none' or null = top-level goal."
|
|
67
|
+
},
|
|
68
|
+
"summary": {
|
|
69
|
+
"type": ["string", "null"],
|
|
70
|
+
"maxLength": 1000,
|
|
71
|
+
"description": "≤1000-char short-form (agentchattr borrow). Mirrored to goals-index.json for O(1) portfolio reads + Tier 1 agent reaction."
|
|
72
|
+
},
|
|
73
|
+
"icon": {
|
|
74
|
+
"type": ["string", "null"],
|
|
75
|
+
"maxLength": 8,
|
|
76
|
+
"description": "Optional emoji/icon for visual differentiation in portfolio cards (bigokr borrow)."
|
|
77
|
+
},
|
|
78
|
+
"cycle": {
|
|
79
|
+
"type": ["string", "null"],
|
|
80
|
+
"maxLength": 32,
|
|
81
|
+
"description": "Optional temporal grouping label e.g. 'Q1 2026', 'v1.7-cycle', '2026 H1' (bigokr borrow). Portfolio groups goals by cycle."
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://github.com/dv-workflow/dv-workflow/blob/main/.dw/core/schemas/task-frontmatter.schema.json",
|
|
4
4
|
"title": "task-frontmatter",
|
|
5
|
-
"description": "Frontmatter schema for .dw/tasks/{name}/task.md (v3 task format per ADR-0008)",
|
|
5
|
+
"description": "Frontmatter schema for .dw/tasks/{name}/task.md (v3 task format per ADR-0008; v3.1 adds optional goal linkage + summary per ADR-0010)",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"task_id",
|
|
@@ -67,12 +67,31 @@
|
|
|
67
67
|
},
|
|
68
68
|
"schema_version": {
|
|
69
69
|
"type": "string",
|
|
70
|
-
"
|
|
71
|
-
"description": "Frontmatter schema version.
|
|
70
|
+
"enum": ["v3.0", "v3.1"],
|
|
71
|
+
"description": "Frontmatter schema version. v3.1 adds optional parent_goal_id, contributing_goal_ids, summary per ADR-0010."
|
|
72
72
|
},
|
|
73
73
|
"blockers": {
|
|
74
74
|
"type": "string",
|
|
75
75
|
"description": "Current blockers in free-form text; use 'none' if unblocked"
|
|
76
|
+
},
|
|
77
|
+
"parent_goal_id": {
|
|
78
|
+
"type": ["string", "null"],
|
|
79
|
+
"pattern": "^G-[A-Za-z0-9](?:[A-Za-z0-9.-]{0,31}[A-Za-z0-9])?$|^none$",
|
|
80
|
+
"description": "v3.1 (ADR-0010 Q1): Primary goal this task contributes to. Single goal; ownership semantics. Format G-{slug} or 'none' or null."
|
|
81
|
+
},
|
|
82
|
+
"contributing_goal_ids": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"items": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"pattern": "^G-[A-Za-z0-9](?:[A-Za-z0-9.-]{0,31}[A-Za-z0-9])?$"
|
|
87
|
+
},
|
|
88
|
+
"uniqueItems": true,
|
|
89
|
+
"description": "v3.1 (ADR-0010 Q1): Optional secondary goals task contributes to. Informational only; no ownership/portfolio-placement effect."
|
|
90
|
+
},
|
|
91
|
+
"summary": {
|
|
92
|
+
"type": ["string", "null"],
|
|
93
|
+
"maxLength": 1000,
|
|
94
|
+
"description": "v3.1 (ADR-0010 + agentchattr borrow): ≤1000-char short-form for portfolio cards + Tier 1 agent reaction. Mirrored to goals-index.json/active-index when applicable."
|
|
76
95
|
}
|
|
77
96
|
}
|
|
78
97
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
goal_id: G-{slug}
|
|
3
|
+
schema_version: goal@v1
|
|
4
|
+
created: {YYYY-MM-DD}
|
|
5
|
+
last_updated: {YYYY-MM-DD}
|
|
6
|
+
status: Draft
|
|
7
|
+
owner: {name}
|
|
8
|
+
target_date: {YYYY-MM-DD or TBD}
|
|
9
|
+
goal_version: 1
|
|
10
|
+
archived_at: null
|
|
11
|
+
parent_goal_id: none
|
|
12
|
+
icon: "🎯"
|
|
13
|
+
cycle: "{Q1 2026 | v1.7-cycle | 2026 H1 | null}"
|
|
14
|
+
summary: "{≤1000-char short-form for portfolio cards + agent reaction; mirrored to goals-index.json}"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Goal: {Title}
|
|
18
|
+
|
|
19
|
+
<!--
|
|
20
|
+
Goals Management Layer (ADR-0010 Accepted Round 2, 2026-05-22).
|
|
21
|
+
|
|
22
|
+
This template mirrors task.md v3 structure (dual-projection per ADR-0008) so
|
|
23
|
+
agents and humans navigate goal.md and task.md with the same mental model.
|
|
24
|
+
|
|
25
|
+
Status lives ONLY in Section 3 (Subtask/KR Tracker) — never in Section 2.
|
|
26
|
+
Status transitions auto-bump goal_version per Q4/C-1; non-status field edits
|
|
27
|
+
emit goal_field_updated event without counter increment.
|
|
28
|
+
|
|
29
|
+
Sidecar SVG (timeline-goal.svg) auto-rendered by `dw goal render` once
|
|
30
|
+
dw-kit-render `renderGoal()` ships (R-G6 telemetry-gated, post-P1).
|
|
31
|
+
-->
|
|
32
|
+
|
|
33
|
+
<!--  -->
|
|
34
|
+
|
|
35
|
+
## 1. Snapshot
|
|
36
|
+
|
|
37
|
+
**Status:** {Draft | Active | Achieved | Abandoned | Pivoted}
|
|
38
|
+
**Owner:** {name}
|
|
39
|
+
**Target date:** {YYYY-MM-DD or TBD}
|
|
40
|
+
**Goal version:** {1}
|
|
41
|
+
**Last updated:** {YYYY-MM-DD}
|
|
42
|
+
**Linked tasks:** {auto-populated from `dw goal lint` or `dw goal portfolio`}
|
|
43
|
+
|
|
44
|
+
## 2. Statement
|
|
45
|
+
|
|
46
|
+
<!--
|
|
47
|
+
Section 2 is the stable intent contract. Per lint convention, status markers
|
|
48
|
+
(✅, 🟡, dates) MUST NOT appear in Section 2 — those live exclusively in
|
|
49
|
+
Section 3 (Tracker). Section 2 changes only when intent or scope genuinely
|
|
50
|
+
changes (auto-bumps goal_version via `dw goal bump --reason`).
|
|
51
|
+
-->
|
|
52
|
+
|
|
53
|
+
### What
|
|
54
|
+
|
|
55
|
+
{1-2 paragraphs — what outcome this goal is chasing. No background padding.}
|
|
56
|
+
|
|
57
|
+
### Why Now
|
|
58
|
+
|
|
59
|
+
{Forcing function: deadline, market signal, dependency, strategic bet.}
|
|
60
|
+
|
|
61
|
+
### Out of Scope
|
|
62
|
+
|
|
63
|
+
- {Explicit exclusions — prevents goal scope creep into Jira-lite territory (R-G1)}
|
|
64
|
+
|
|
65
|
+
## 3. Key Results & Linked Tasks Tracker
|
|
66
|
+
|
|
67
|
+
<!--
|
|
68
|
+
SINGLE SOURCE OF TRUTH for KR status. This is the only section where status
|
|
69
|
+
markers (⬜🟡✅🔴⏸) are allowed. KR progress is percentage 0-100% per Q2.
|
|
70
|
+
Linked tasks auto-populated from frontmatter parent_goal_id / contributing_goal_ids
|
|
71
|
+
in task.md v3.1.
|
|
72
|
+
-->
|
|
73
|
+
|
|
74
|
+
### Key Results
|
|
75
|
+
|
|
76
|
+
| # | Key Result | Target | Current | Status | Notes |
|
|
77
|
+
|---|-----------|--------|---------|--------|-------|
|
|
78
|
+
| KR-001 | {measurable outcome} | {100%} | {0%} | ⬜ Pending | |
|
|
79
|
+
| KR-002 | ... | ... | ... | ⬜ Pending | |
|
|
80
|
+
|
|
81
|
+
Status legend: ⬜ Pending · 🟡 In Progress · ✅ Achieved · 🔴 Blocked · ⏸ Paused
|
|
82
|
+
|
|
83
|
+
### Linked Tasks
|
|
84
|
+
|
|
85
|
+
| Task | Role | KR | Status |
|
|
86
|
+
|------|------|----|--------|
|
|
87
|
+
| {auto-populated by `dw goal lint`} | primary/contributing | KR-N | ... |
|
|
88
|
+
|
|
89
|
+
## 4. Timeline / Changelog
|
|
90
|
+
|
|
91
|
+
<!--
|
|
92
|
+
Reverse-chronological. Each pivot or material change = one heading. Section
|
|
93
|
+
auto-rotates per goal_version bump (manual `dw goal bump --reason` or status
|
|
94
|
+
transition). Field edits emit events to events-global.jsonl WITHOUT bumping
|
|
95
|
+
this section (Q4/C-1 separation).
|
|
96
|
+
-->
|
|
97
|
+
|
|
98
|
+
```mermaid
|
|
99
|
+
timeline
|
|
100
|
+
title Goal Lifecycle
|
|
101
|
+
{YYYY-MM-DD} : Drafted (v1)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### {YYYY-MM-DD} — Goal drafted (version 1)
|
|
105
|
+
|
|
106
|
+
**Statement:**
|
|
107
|
+
{snapshot of Section 2 at creation time for diff history}
|
|
108
|
+
|
|
109
|
+
**Key Results defined:**
|
|
110
|
+
- KR-001: ...
|
|
111
|
+
- KR-002: ...
|
|
112
|
+
|
|
113
|
+
**Initial target date:** {YYYY-MM-DD or TBD}
|
|
114
|
+
|
|
115
|
+
## 5. Handoff & Annotations
|
|
116
|
+
|
|
117
|
+
<!--
|
|
118
|
+
Cross-session context for agents picking up work on this goal. Annotations
|
|
119
|
+
(canvas-style sticky notes) live in peer file goal-annotations.md per Q6
|
|
120
|
+
(ADR-0010 P6 scope).
|
|
121
|
+
-->
|
|
122
|
+
|
|
123
|
+
**For next session (or next agent):**
|
|
124
|
+
|
|
125
|
+
- **Read first:** {related ADRs, parent goal if any, linked task.md files}
|
|
126
|
+
- **Current state:** {phase, blocked-on, last decision}
|
|
127
|
+
- **Don't do:** {anti-patterns specific to this goal}
|
|
128
|
+
- **Watch out:** {strategic risks, dependencies, stakeholder commitments}
|
|
129
|
+
|
|
130
|
+
### Friction Journal
|
|
131
|
+
|
|
132
|
+
| Date | Friction | Component | Proposed fix |
|
|
133
|
+
|------|----------|-----------|-------------|
|
|
134
|
+
| {YYYY-MM-DD} | ... | ... | ... |
|
|
135
|
+
|
|
136
|
+
## 6. Annexes
|
|
137
|
+
|
|
138
|
+
<!--
|
|
139
|
+
Free-form supplements. Recommended names:
|
|
140
|
+
- baseline.md — initial metrics
|
|
141
|
+
- pivot-r{N}.md — pivot rationale
|
|
142
|
+
- goal-annotations.md — canvas annotations (P6+)
|
|
143
|
+
- timeline-history.md — auto-rotated overflow
|
|
144
|
+
-->
|
|
145
|
+
|
|
146
|
+
- (none)
|
package/CLAUDE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Workflow toolkit codebase. Rules live in `.claude/rules/` (auto-loaded).
|
|
4
4
|
|
|
5
5
|
**v2.0 direction:** Context-First SDLC Governance Layer (5 pillars — see `.dw/core/PILLARS.md`)
|
|
6
|
-
**Current:** v1.
|
|
6
|
+
**Current:** v1.6.0-rc.1 on npm `rc` tag (2026-05-20) + **v1.7.0-rc.1 candidate** on `feat/goals-okr-v1.7` shipping ADR-0010 Goals Management Layer (Accepted Round 2 — all 4 Critical + 7 Warnings + 4 Suggestions resolved; `bcurts/agentchattr` summary primitive + bigokr icon/cycle/progress/constellation visualization borrowed; 150/150 smoke tests). Active ADRs: ADR-0001 (Pragmatic Lean), ADR-0005/0006 (Supply-Chain Guard; sunset review 2026-08-12), ADR-0008 (Task Docs v3, v1.5), ADR-0009 (Agent OS, v1.6), ADR-0010 (Goals Layer, v1.7). v1.4 cuts pending telemetry.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -121,13 +121,14 @@ export function run(argv) {
|
|
|
121
121
|
|
|
122
122
|
taskCmd
|
|
123
123
|
.command('migrate [task-name]')
|
|
124
|
-
.description('Migrate v2 spec.md + tracking.md → v3 task.md')
|
|
124
|
+
.description('Migrate v2 spec.md + tracking.md → v3 task.md, OR bump v3.0 → v3.1 schema (--to-v3.1)')
|
|
125
125
|
.option('-n, --dry-run', 'Preview without writing')
|
|
126
126
|
.option('--diff', 'Show diff against existing task.md')
|
|
127
|
-
.option('--all', 'Scan and migrate all v2 tasks')
|
|
127
|
+
.option('--all', 'Scan and migrate all v2 tasks (or all v3.0 tasks when --to-v3.1)')
|
|
128
128
|
.option('--force', 'Overwrite existing task.md')
|
|
129
129
|
.option('--remove-v2', 'Delete spec.md/tracking.md after backup (default keeps them)')
|
|
130
130
|
.option('--rollback', 'Restore v2 files from .v2bak backups (requires task name)')
|
|
131
|
+
.option('--to-v3-1', 'Bump v3.0 → v3.1 schema (adds optional parent_goal_id, contributing_goal_ids, summary fields per ADR-0010)')
|
|
131
132
|
.action(async (taskName, opts) => {
|
|
132
133
|
const { taskMigrateCommand } = await import('./commands/task-migrate.mjs');
|
|
133
134
|
await taskMigrateCommand(taskName, opts);
|
|
@@ -178,14 +179,24 @@ export function run(argv) {
|
|
|
178
179
|
.command('watch [task-name]')
|
|
179
180
|
.description('Watch task.md for changes and live-reload the HTML preview in browser (local server, debounced)')
|
|
180
181
|
.option('-p, --port <port>', 'Local server port (auto-finds next available if busy)', parseInt)
|
|
182
|
+
.option('--rotate-token', 'Regenerate .dw/cache/watch.token at startup (invalidates old browser sessions; C-3)')
|
|
181
183
|
.action(async (taskName, opts) => {
|
|
182
184
|
const { taskWatchCommand } = await import('./commands/task-watch.mjs');
|
|
183
185
|
await taskWatchCommand(taskName, opts);
|
|
184
186
|
});
|
|
185
187
|
|
|
188
|
+
taskCmd
|
|
189
|
+
.command('summary <task-name>')
|
|
190
|
+
.description('Read or update task summary frontmatter field (≤1000 chars; agentchattr borrow)')
|
|
191
|
+
.option('--write <text>', 'Update summary (auto-bumps schema_version v3.0→v3.1 if needed)')
|
|
192
|
+
.action(async (taskName, opts) => {
|
|
193
|
+
const { taskSummaryCommand } = await import('./commands/task-summary.mjs');
|
|
194
|
+
await taskSummaryCommand(taskName, opts);
|
|
195
|
+
});
|
|
196
|
+
|
|
186
197
|
const agentCmd = program
|
|
187
198
|
.command('agent')
|
|
188
|
-
.description('Agent OS multi-agent orchestration (ADR-0009): claim · release · claims · reports · conflicts');
|
|
199
|
+
.description('Agent OS multi-agent orchestration (ADR-0009): claim · release · renew · expire · claims · reports · conflicts · check-staged · verify');
|
|
189
200
|
|
|
190
201
|
agentCmd
|
|
191
202
|
.command('claim <task-id>')
|
|
@@ -213,6 +224,15 @@ export function run(argv) {
|
|
|
213
224
|
await agentReleaseCommand(claimId, opts);
|
|
214
225
|
});
|
|
215
226
|
|
|
227
|
+
agentCmd
|
|
228
|
+
.command('renew <claim-id>')
|
|
229
|
+
.description('Extend lease on an active claim WITHOUT changing claim_id (preserves audit continuity vs expire+reclaim). Capped at 3 renewals (issue #15)')
|
|
230
|
+
.option('-l, --lease <duration>', 'New lease window (e.g. 30m, 1h, 4h)', '1h')
|
|
231
|
+
.action(async (claimId, opts) => {
|
|
232
|
+
const { agentRenewCommand } = await import('./commands/agent-claim.mjs');
|
|
233
|
+
await agentRenewCommand(claimId, opts);
|
|
234
|
+
});
|
|
235
|
+
|
|
216
236
|
agentCmd
|
|
217
237
|
.command('expire <claim-id>')
|
|
218
238
|
.description('Mark a claim as expired or invalidated (orchestrator action)')
|
|
@@ -254,6 +274,165 @@ export function run(argv) {
|
|
|
254
274
|
await agentConflictsCommand(opts);
|
|
255
275
|
});
|
|
256
276
|
|
|
277
|
+
agentCmd
|
|
278
|
+
.command('check-staged [files...]')
|
|
279
|
+
.description('Check staged files against active claim write_scopes (R2-2 cooperative; used by pre-commit-gate hook). Always exits 0; warning on stdout.')
|
|
280
|
+
.option('--stdin', 'Read file list from stdin (one path per line) instead of positional args')
|
|
281
|
+
.action(async (files, opts) => {
|
|
282
|
+
const { agentCheckStagedCommand } = await import('./commands/agent-check-staged.mjs');
|
|
283
|
+
await agentCheckStagedCommand(files, opts);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
agentCmd
|
|
287
|
+
.command('verify <task-id>')
|
|
288
|
+
.description('Check events.jsonl well-formedness for a task (detects malformed JSON lines; exits 1 if any). Note: not full tamper-evidence — use git log for that.')
|
|
289
|
+
.option('-v, --verbose', 'Show raw line content for malformed entries')
|
|
290
|
+
.option('--no-strict', 'Report malformed lines but always exit 0')
|
|
291
|
+
.action(async (taskId, opts) => {
|
|
292
|
+
const { agentVerifyCommand } = await import('./commands/agent-verify.mjs');
|
|
293
|
+
await agentVerifyCommand(taskId, opts);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const goalCmd = program
|
|
297
|
+
.command('goal')
|
|
298
|
+
.description('Goals Management Layer (ADR-0010): strategic layer above tasks. new · show · link · summary · portfolio · lint · bump · delete · view');
|
|
299
|
+
|
|
300
|
+
goalCmd
|
|
301
|
+
.command('new <goal-id>')
|
|
302
|
+
.description('Create a new goal from template (ID format G-{slug})')
|
|
303
|
+
.option('--title <text>', 'Goal title (default: "New Goal")')
|
|
304
|
+
.option('--owner <name>', 'Owner (default: current user)')
|
|
305
|
+
.option('--target-date <YYYY-MM-DD>', 'Target date (default: TBD)')
|
|
306
|
+
.option('--icon <emoji>', 'Icon/emoji for portfolio cards (default: 🎯)')
|
|
307
|
+
.option('--cycle <label>', 'Cycle label e.g. "Q2 2026", "v1.7-cycle"')
|
|
308
|
+
.option('--summary <text>', 'Initial ≤1000-char summary')
|
|
309
|
+
.option('--parent-goal-id <id>', 'Parent goal ID for sub-goals')
|
|
310
|
+
.action(async (goalId, opts) => {
|
|
311
|
+
const { goalNewCommand } = await import('./commands/goal-new.mjs');
|
|
312
|
+
await goalNewCommand(goalId, opts);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
goalCmd
|
|
316
|
+
.command('set <goal-id>')
|
|
317
|
+
.description('Update goal metadata fields (icon, cycle, owner, target_date, parent_goal_id)')
|
|
318
|
+
.option('--icon <emoji>', 'Icon/emoji (empty string removes)')
|
|
319
|
+
.option('--cycle <label>', 'Cycle label (empty string removes)')
|
|
320
|
+
.option('--owner <name>', 'New owner')
|
|
321
|
+
.option('--target-date <YYYY-MM-DD>', 'New target date or "TBD"')
|
|
322
|
+
.option('--parent-goal-id <id>', 'Parent goal ID or "none"')
|
|
323
|
+
.action(async (goalId, opts) => {
|
|
324
|
+
const { goalSetCommand } = await import('./commands/goal-set.mjs');
|
|
325
|
+
await goalSetCommand(goalId, opts);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
goalCmd
|
|
329
|
+
.command('show [goal-id]')
|
|
330
|
+
.description('ANSI snapshot of a goal (no arg = list all)')
|
|
331
|
+
.action(async (goalId, opts) => {
|
|
332
|
+
const { goalShowCommand } = await import('./commands/goal-show.mjs');
|
|
333
|
+
await goalShowCommand(goalId, opts);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
goalCmd
|
|
337
|
+
.command('link <goal-id> <task-id>')
|
|
338
|
+
.description('Link a task → goal (primary parent_goal_id; --contributing for secondary)')
|
|
339
|
+
.option('--contributing', 'Add to contributing_goal_ids instead of parent_goal_id (Q1)')
|
|
340
|
+
.option('-f, --force', 'Override existing parent_goal_id')
|
|
341
|
+
.action(async (goalId, taskId, opts) => {
|
|
342
|
+
const { goalLinkCommand } = await import('./commands/goal-link.mjs');
|
|
343
|
+
await goalLinkCommand(goalId, taskId, opts);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
goalCmd
|
|
347
|
+
.command('unlink <goal-id> <task-id>')
|
|
348
|
+
.description('Remove task→goal mapping (clears both parent_goal_id and contributing_goal_ids)')
|
|
349
|
+
.action(async (goalId, taskId) => {
|
|
350
|
+
const { goalUnlinkCommand } = await import('./commands/goal-link.mjs');
|
|
351
|
+
await goalUnlinkCommand(goalId, taskId);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
goalCmd
|
|
355
|
+
.command('summary <goal-id>')
|
|
356
|
+
.description('Read or update goal summary (≤1000 chars; agentchattr borrow)')
|
|
357
|
+
.option('--write <text>', 'Update summary (≤1000 chars); auto-syncs to goals-index.json')
|
|
358
|
+
.action(async (goalId, opts) => {
|
|
359
|
+
const { goalSummaryCommand } = await import('./commands/goal-summary.mjs');
|
|
360
|
+
await goalSummaryCommand(goalId, opts);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
goalCmd
|
|
364
|
+
.command('bump <goal-id>')
|
|
365
|
+
.description('Manually bump goal_version (Q4 material change; requires --reason)')
|
|
366
|
+
.option('--reason <text>', 'Reason for the version bump (required)')
|
|
367
|
+
.action(async (goalId, opts) => {
|
|
368
|
+
const { goalBumpCommand } = await import('./commands/goal-bump.mjs');
|
|
369
|
+
await goalBumpCommand(goalId, opts);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
goalCmd
|
|
373
|
+
.command('delete <goal-id>')
|
|
374
|
+
.description('Delete a goal (C-2: soft by default, --hard removes folder)')
|
|
375
|
+
.option('--cascade', 'Clear parent_goal_id / contributing_goal_ids on linked tasks')
|
|
376
|
+
.option('--hard', 'Hard-delete: remove .dw/goals/{id}/ folder (vs soft = status→Abandoned)')
|
|
377
|
+
.option('-n, --dry-run', 'Preview without writing')
|
|
378
|
+
.action(async (goalId, opts) => {
|
|
379
|
+
const { goalDeleteCommand } = await import('./commands/goal-delete.mjs');
|
|
380
|
+
await goalDeleteCommand(goalId, opts);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
goalCmd
|
|
384
|
+
.command('lint [goal-id]')
|
|
385
|
+
.description('Lint goal.md: frontmatter schema + summary length + index drift + orphan cross-refs')
|
|
386
|
+
.action(async (goalId, opts) => {
|
|
387
|
+
const { goalLintCommand } = await import('./commands/goal-lint.mjs');
|
|
388
|
+
await goalLintCommand(goalId, opts);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
goalCmd
|
|
392
|
+
.command('portfolio')
|
|
393
|
+
.description('Show all goals at-a-glance (O(1) read via goals-index.json; W-1)')
|
|
394
|
+
.option('--status <status>', 'Filter by status (Draft | Active | Achieved | Abandoned | Pivoted)')
|
|
395
|
+
.action(async (opts) => {
|
|
396
|
+
const { goalPortfolioCommand } = await import('./commands/goal-portfolio.mjs');
|
|
397
|
+
await goalPortfolioCommand(opts);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
goalCmd
|
|
401
|
+
.command('view [goal-id]')
|
|
402
|
+
.description('Generate HTML portfolio (P1+P2 joint MVP per W-7)')
|
|
403
|
+
.option('--no-open', 'Do not auto-open browser')
|
|
404
|
+
.action(async (goalId, opts) => {
|
|
405
|
+
const { goalViewCommand } = await import('./commands/goal-view.mjs');
|
|
406
|
+
await goalViewCommand(goalId, opts);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
goalCmd
|
|
410
|
+
.command('migrate [goal-id]')
|
|
411
|
+
.description('Stub for future schema bump (S-1; goal@v1 → goal@v2)')
|
|
412
|
+
.option('--to <schema>', 'Target schema (default: goal@v2)')
|
|
413
|
+
.action(async (goalId, opts) => {
|
|
414
|
+
const { goalMigrateCommand } = await import('./commands/goal-stubs.mjs');
|
|
415
|
+
await goalMigrateCommand(goalId, opts);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
goalCmd
|
|
419
|
+
.command('suggest-krs <goal-id>')
|
|
420
|
+
.description('Build SMART-formatted prompt to paste into AI agent chat for KR brainstorming (W-5)')
|
|
421
|
+
.option('--count <n>', 'Requested number of KRs (default: 3)', '3')
|
|
422
|
+
.option('--json', 'Output structured JSON (for agent piping)')
|
|
423
|
+
.action(async (goalId, opts) => {
|
|
424
|
+
const { goalSuggestKrsCommand } = await import('./commands/goal-suggest-krs.mjs');
|
|
425
|
+
await goalSuggestKrsCommand(goalId, opts);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
goalCmd
|
|
429
|
+
.command('render [goal-id]')
|
|
430
|
+
.description('Render constellation SVG for goal(s) — goal center + KR satellites + task leaves (R-G6 shipped)')
|
|
431
|
+
.action(async (goalId, opts) => {
|
|
432
|
+
const { goalRenderCommand } = await import('./commands/goal-render.mjs');
|
|
433
|
+
await goalRenderCommand(goalId, opts);
|
|
434
|
+
});
|
|
435
|
+
|
|
257
436
|
program
|
|
258
437
|
.command('dashboard')
|
|
259
438
|
.description('Show team dashboard — active tasks, ADRs, telemetry summary, health')
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { listClaims } from '../lib/agent-claim.mjs';
|
|
3
|
+
import { pathMatchesScope } from '../lib/agent-conflict.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `dw agent check-staged <files...>` or `dw agent check-staged --stdin`
|
|
7
|
+
*
|
|
8
|
+
* Issue #13 Bug 3: pre-commit-gate.sh R2-2 block used inline `node -e` with
|
|
9
|
+
* `import('$CLAUDE_PROJECT_DIR/src/lib/agent-claim.mjs')` — dead code in
|
|
10
|
+
* consumer projects (cwd-relative path resolves to non-existent file) and
|
|
11
|
+
* exposed path-injection via shell variable substitution into JS string.
|
|
12
|
+
* This subcommand replaces the inline JS — hooks now invoke `dw agent
|
|
13
|
+
* check-staged --stdin` cleanly via the installed binary.
|
|
14
|
+
*
|
|
15
|
+
* Output contract (cooperative-only per ADR-0009 R2):
|
|
16
|
+
* - stdout: warning text if any staged files fall outside active claim
|
|
17
|
+
* write_scopes; empty otherwise
|
|
18
|
+
* - exit code: ALWAYS 0 (cooperative protocol — never blocks commits)
|
|
19
|
+
*
|
|
20
|
+
* Hook detects out-of-scope by checking stdout is non-empty.
|
|
21
|
+
*/
|
|
22
|
+
export async function agentCheckStagedCommand(files, opts = {}) {
|
|
23
|
+
const rootDir = process.cwd();
|
|
24
|
+
let inputFiles = Array.isArray(files) ? [...files] : [];
|
|
25
|
+
|
|
26
|
+
if (opts.stdin) {
|
|
27
|
+
const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
28
|
+
for await (const line of rl) {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (trimmed) inputFiles.push(trimmed);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// No files to check → silent success
|
|
35
|
+
if (inputFiles.length === 0) {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// No active claims → nothing to check against; silent success
|
|
41
|
+
const claims = listClaims(rootDir).filter(
|
|
42
|
+
(c) => c._live_status === 'created' || c._live_status === 'active'
|
|
43
|
+
);
|
|
44
|
+
if (claims.length === 0) {
|
|
45
|
+
process.exit(0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Aggregate all active write_scopes. pathMatchesScope normalizes separators
|
|
50
|
+
// internally (Bug 2 fix) so legacy backslash claims still match correctly.
|
|
51
|
+
const allScopes = claims.flatMap((c) => c.write_scope);
|
|
52
|
+
const outside = inputFiles.filter((f) => !pathMatchesScope(f, allScopes));
|
|
53
|
+
|
|
54
|
+
if (outside.length > 0) {
|
|
55
|
+
console.log('Files NOT in any active claim write_scope:');
|
|
56
|
+
for (const f of outside.slice(0, 10)) console.log(' ' + f);
|
|
57
|
+
if (outside.length > 10) console.log(' ... +' + (outside.length - 10) + ' more');
|
|
58
|
+
// Black-bot C-4: exit 0 here intentional (cooperative protocol; hook detects
|
|
59
|
+
// out-of-scope via non-empty stdout, not exit code). Single exit at end below.
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|