@goodtek/vibeops 1.1.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +28 -12
- package/dist/bootstrap/installer.js +1 -1
- package/dist/cli.js +58 -8
- package/dist/commands/init.js +3 -3
- package/dist/commands/llm.js +3 -3
- package/dist/commands/status.js +10 -2
- package/dist/commands/task-add.js +2 -2
- package/dist/commands/task-merge.js +134 -0
- package/dist/commands/task-release.js +103 -0
- package/dist/commands/{task-done.js → task-ship.js} +28 -63
- package/dist/commands/task-sync.js +168 -0
- package/dist/lib/git.js +7 -0
- package/dist/lib/paths.js +1 -1
- package/dist/lib/pr-create.js +63 -0
- package/dist/lib/resolve-task.js +2 -2
- package/dist/lib/task-add-llm.js +1 -1
- package/dist/lib/task-context.js +33 -8
- package/dist/lib/task-effective-status.js +62 -0
- package/dist/lib/task-git-commit.js +32 -0
- package/dist/lib/task-lifecycle-target.js +41 -0
- package/dist/lib/task-scaffold.js +2 -2
- package/dist/lib/{task-done-llm.js → task-ship-llm.js} +9 -6
- package/dist/lib/task-start.js +1 -1
- package/dist/lib/task.js +22 -34
- package/package.json +1 -1
- package/templates/clients/claude/CLAUDE.md +5 -27
- package/templates/clients/cursor/.cursor/rules/02-git-safety.mdc +5 -5
- package/templates/clients/cursor/.cursor/rules/03-docs-before-ship.mdc +13 -0
- package/templates/core/AGENTS.md +16 -34
- package/templates/core/docs/logs/README.md +1 -1
- package/templates/core/docs/project/03-architecture.md +1 -1
- package/templates/core/docs/project/05-current-state.md +1 -1
- package/templates/core/docs/project/06-decisions.md +1 -1
- package/templates/skills-shared/implement-task/SKILL.md +7 -16
- package/templates/clients/cursor/.cursor/rules/03-docs-on-done.mdc +0 -13
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readGitInfo } from "./git.js";
|
|
2
|
+
import { isOnTaskBranch, loadActionableTasks, pickLatestShippedTask, readGitContext, } from "./task.js";
|
|
3
|
+
import { resolveTask } from "./resolve-task.js";
|
|
4
|
+
/** Resolve TASK + task branch for ship / merge / sync. */
|
|
5
|
+
export async function resolveLifecycleTarget(paths, cwd, taskRef) {
|
|
6
|
+
const trimmed = taskRef?.trim();
|
|
7
|
+
if (trimmed !== undefined && trimmed.length > 0) {
|
|
8
|
+
const resolved = await resolveTask(paths, cwd, trimmed);
|
|
9
|
+
if (resolved === null)
|
|
10
|
+
return null;
|
|
11
|
+
const ctx = await readGitContext(resolved.taskFile);
|
|
12
|
+
if (ctx === null)
|
|
13
|
+
return null;
|
|
14
|
+
return {
|
|
15
|
+
taskId: resolved.taskId,
|
|
16
|
+
taskBranch: ctx.taskBranch,
|
|
17
|
+
taskFile: resolved.taskFile,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const git = await readGitInfo(cwd);
|
|
21
|
+
if (git.isRepo && typeof git.branch === "string" && git.branch.startsWith("task/")) {
|
|
22
|
+
const tasks = await loadActionableTasks(paths.docsTasks);
|
|
23
|
+
for (const t of tasks) {
|
|
24
|
+
const ctx = await readGitContext(t.filePath);
|
|
25
|
+
if (isOnTaskBranch(git.branch, ctx)) {
|
|
26
|
+
return { taskId: t.id, taskBranch: git.branch, taskFile: t.filePath };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const shipped = pickLatestShippedTask(await loadActionableTasks(paths.docsTasks));
|
|
31
|
+
if (shipped === null)
|
|
32
|
+
return null;
|
|
33
|
+
const ctx = await readGitContext(shipped.filePath);
|
|
34
|
+
if (ctx === null)
|
|
35
|
+
return null;
|
|
36
|
+
return {
|
|
37
|
+
taskId: shipped.id,
|
|
38
|
+
taskBranch: ctx.taskBranch,
|
|
39
|
+
taskFile: shipped.filePath,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -28,7 +28,7 @@ export function buildMinimalTaskMarkdown(draft) {
|
|
|
28
28
|
|
|
29
29
|
## Status
|
|
30
30
|
|
|
31
|
-
${statusDisplay("
|
|
31
|
+
${statusDisplay("in_progress")}
|
|
32
32
|
|
|
33
33
|
## Goal
|
|
34
34
|
|
|
@@ -45,7 +45,7 @@ ${draft.idea.trim()}
|
|
|
45
45
|
## Acceptance Criteria
|
|
46
46
|
|
|
47
47
|
1. Goal and Scope are met and verifiable via Test Plan.
|
|
48
|
-
2. Result and Test Result are filled before \`vibeops task
|
|
48
|
+
2. Result and Test Result are filled before \`vibeops task ship\`.
|
|
49
49
|
|
|
50
50
|
## Test Plan
|
|
51
51
|
|
|
@@ -17,7 +17,7 @@ function localDateFilename() {
|
|
|
17
17
|
async function readOptional(path) {
|
|
18
18
|
return readTextOrNull(path);
|
|
19
19
|
}
|
|
20
|
-
export async function
|
|
20
|
+
export async function llmCompleteTaskShip(params) {
|
|
21
21
|
const git = await readGitInfo(params.cwd);
|
|
22
22
|
let diffSummary = "";
|
|
23
23
|
if (git.isRepo) {
|
|
@@ -31,7 +31,7 @@ export async function llmCompleteTaskDone(params) {
|
|
|
31
31
|
const messages = [
|
|
32
32
|
{
|
|
33
33
|
role: "system",
|
|
34
|
-
content: `You
|
|
34
|
+
content: `You ship a VibeOps TASK (Status → Shipped). Reply with JSON only:
|
|
35
35
|
{
|
|
36
36
|
"result": "markdown for TASK Result section — facts, paths, commands",
|
|
37
37
|
"testResult": "markdown for Test Result — commands and outcomes",
|
|
@@ -91,7 +91,7 @@ Be factual. Do not invent files not in the diff.`,
|
|
|
91
91
|
: null,
|
|
92
92
|
logLine: typeof parsed.logLine === "string" && parsed.logLine.trim().length > 0
|
|
93
93
|
? parsed.logLine.trim()
|
|
94
|
-
: `${params.taskId}
|
|
94
|
+
: `${params.taskId} shipped.`,
|
|
95
95
|
provider,
|
|
96
96
|
};
|
|
97
97
|
}
|
|
@@ -99,7 +99,7 @@ Be factual. Do not invent files not in the diff.`,
|
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
export async function
|
|
102
|
+
export async function applyTaskShipMemory(cwd, patch) {
|
|
103
103
|
const paths = projectPaths(cwd);
|
|
104
104
|
const updated = [];
|
|
105
105
|
const skipped = [];
|
|
@@ -142,12 +142,15 @@ export async function writeTaskResultSections(taskFile, result, testResult) {
|
|
|
142
142
|
await updateTaskSection(taskFile, "Result", result);
|
|
143
143
|
await updateTaskSection(taskFile, "Test Result", testResult);
|
|
144
144
|
}
|
|
145
|
-
/** Minimal fallback when LLM unavailable — git-only sections from diff. */
|
|
146
145
|
export async function fallbackResultSections(cwd, taskId, taskFile) {
|
|
147
146
|
const names = await gitDiffNameOnly(cwd);
|
|
148
147
|
const body = await readText(taskFile);
|
|
149
148
|
const goal = readSection(body, "Goal").slice(0, 200);
|
|
150
|
-
const result = [
|
|
149
|
+
const result = [
|
|
150
|
+
`Shipped ${taskId}.`,
|
|
151
|
+
goal.length > 0 ? `Goal: ${goal}` : "",
|
|
152
|
+
names.length > 0 ? `Paths: ${names.slice(0, 15).join(", ")}` : "",
|
|
153
|
+
]
|
|
151
154
|
.filter((l) => l.length > 0)
|
|
152
155
|
.join("\n");
|
|
153
156
|
const testResult = names.length > 0 ? `Changed: ${names.join(", ")}` : "Manual verification required.";
|
package/dist/lib/task-start.js
CHANGED
|
@@ -81,7 +81,7 @@ export async function startTaskBranch(opts) {
|
|
|
81
81
|
log.info(` ${dim("task branch")} ${cyan(taskBranch)}`);
|
|
82
82
|
if (branchExists) {
|
|
83
83
|
await gitCheckout(cwd, taskBranch);
|
|
84
|
-
if (meta.status
|
|
84
|
+
if (meta.status !== "shipped") {
|
|
85
85
|
await updateInlineStatus(taskFile, "in_progress");
|
|
86
86
|
}
|
|
87
87
|
if (existingCtx === null) {
|
package/dist/lib/task.js
CHANGED
|
@@ -3,33 +3,31 @@ import { basename, extname, join } from "node:path";
|
|
|
3
3
|
import matter from "gray-matter";
|
|
4
4
|
import { isDirectory, readText, writeText } from "./filesystem.js";
|
|
5
5
|
import { readGitInfo } from "./git.js";
|
|
6
|
-
const
|
|
7
|
-
"planned",
|
|
8
|
-
"in_progress",
|
|
6
|
+
const SHIPPED_ALIASES = new Set([
|
|
9
7
|
"review",
|
|
10
|
-
"
|
|
8
|
+
"merged",
|
|
11
9
|
"done",
|
|
10
|
+
"shipped",
|
|
11
|
+
"ready_for_review",
|
|
12
12
|
]);
|
|
13
13
|
export function normalizeStatus(value) {
|
|
14
14
|
if (typeof value === "string") {
|
|
15
15
|
const s = value.toLowerCase().replace(/\s+/g, "_");
|
|
16
|
-
if (
|
|
17
|
-
return
|
|
16
|
+
if (s === "in_progress" || s === "inprogress")
|
|
17
|
+
return "in_progress";
|
|
18
|
+
if (SHIPPED_ALIASES.has(s))
|
|
19
|
+
return "shipped";
|
|
20
|
+
if (s === "planned" || s === "blocked")
|
|
21
|
+
return "in_progress";
|
|
18
22
|
}
|
|
19
|
-
return "
|
|
23
|
+
return "in_progress";
|
|
20
24
|
}
|
|
21
25
|
export function statusDisplay(status) {
|
|
22
26
|
switch (status) {
|
|
23
|
-
case "planned":
|
|
24
|
-
return "Planned";
|
|
25
27
|
case "in_progress":
|
|
26
28
|
return "In Progress";
|
|
27
|
-
case "
|
|
28
|
-
return "
|
|
29
|
-
case "blocked":
|
|
30
|
-
return "Blocked";
|
|
31
|
-
case "done":
|
|
32
|
-
return "Done";
|
|
29
|
+
case "shipped":
|
|
30
|
+
return "Shipped";
|
|
33
31
|
}
|
|
34
32
|
}
|
|
35
33
|
function extractIdFromFilename(file) {
|
|
@@ -89,7 +87,7 @@ export async function readTaskFile(filePath) {
|
|
|
89
87
|
const title = titleFromFm ?? extractTitleFromBody(body);
|
|
90
88
|
const status = data["status"] !== undefined
|
|
91
89
|
? normalizeStatus(data["status"])
|
|
92
|
-
: (extractInlineStatus(body) ?? "
|
|
90
|
+
: (extractInlineStatus(body) ?? "in_progress");
|
|
93
91
|
const mvpPhaseFromFm = typeof data["mvpPhase"] === "string" ? data["mvpPhase"] : undefined;
|
|
94
92
|
const mvpPhase = mvpPhaseFromFm ?? extractInlineMvpPhase(body);
|
|
95
93
|
const priority = typeof data["priority"] === "string" ? data["priority"] : undefined;
|
|
@@ -117,11 +115,8 @@ export async function scanTasks(tasksDir) {
|
|
|
117
115
|
export function countTasks(tasks) {
|
|
118
116
|
const counts = {
|
|
119
117
|
total: tasks.length,
|
|
120
|
-
planned: 0,
|
|
121
118
|
in_progress: 0,
|
|
122
|
-
|
|
123
|
-
blocked: 0,
|
|
124
|
-
done: 0,
|
|
119
|
+
shipped: 0,
|
|
125
120
|
};
|
|
126
121
|
for (const t of tasks)
|
|
127
122
|
counts[t.status]++;
|
|
@@ -131,14 +126,7 @@ export function pickInProgressTask(tasks) {
|
|
|
131
126
|
return tasks.find((t) => t.status === "in_progress") ?? null;
|
|
132
127
|
}
|
|
133
128
|
export function pickNextTask(tasks) {
|
|
134
|
-
|
|
135
|
-
if (inProgress)
|
|
136
|
-
return inProgress;
|
|
137
|
-
const review = tasks.find((t) => t.status === "review");
|
|
138
|
-
if (review)
|
|
139
|
-
return review;
|
|
140
|
-
const planned = tasks.filter((t) => t.status === "planned");
|
|
141
|
-
return planned[0] ?? null;
|
|
129
|
+
return pickInProgressTask(tasks);
|
|
142
130
|
}
|
|
143
131
|
const TEMPLATE_TASK_ID = "TASK-000";
|
|
144
132
|
export function filterActionableTasks(tasks) {
|
|
@@ -148,12 +136,12 @@ function taskSortKey(id) {
|
|
|
148
136
|
const m = /^TASK-(\d+)$/i.exec(id.trim());
|
|
149
137
|
return m ? Number.parseInt(m[1], 10) : 0;
|
|
150
138
|
}
|
|
151
|
-
/** Latest
|
|
152
|
-
export function
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
139
|
+
/** Latest **Shipped** TASK (for merge / sync targeting). */
|
|
140
|
+
export function pickLatestShippedTask(tasks) {
|
|
141
|
+
const shipped = filterActionableTasks(tasks).filter((t) => t.status === "shipped");
|
|
142
|
+
if (shipped.length === 0)
|
|
155
143
|
return null;
|
|
156
|
-
return [...
|
|
144
|
+
return [...shipped].sort((a, b) => taskSortKey(b.id) - taskSortKey(a.id))[0];
|
|
157
145
|
}
|
|
158
146
|
export async function loadActionableTasks(tasksDir) {
|
|
159
147
|
return filterActionableTasks(await scanTasks(tasksDir));
|
|
@@ -178,7 +166,7 @@ export async function pickActiveTask(tasksDir, cwd) {
|
|
|
178
166
|
return t;
|
|
179
167
|
}
|
|
180
168
|
}
|
|
181
|
-
return pickNextTask(actionable);
|
|
169
|
+
return pickNextTask(actionable.filter((t) => t.status === "in_progress"));
|
|
182
170
|
}
|
|
183
171
|
export async function findTaskFile(tasksDir, taskId) {
|
|
184
172
|
const all = await scanTasks(tasksDir);
|
package/package.json
CHANGED
|
@@ -1,28 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Claude Code — VibeOps
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
| Skill | When |
|
|
8
|
-
|-------|------|
|
|
9
|
-
| `/plan-task` | Refine `docs/tasks/TASK-NNN-*.md` before coding |
|
|
10
|
-
| `/implement-task` | Build per TASK Scope and Acceptance Criteria |
|
|
11
|
-
|
|
12
|
-
Skills live in `.claude/skills/`.
|
|
13
|
-
|
|
14
|
-
## Session start
|
|
15
|
-
|
|
16
|
-
1. `docs/project/05-current-state.md`
|
|
17
|
-
2. The **full** current `docs/tasks/TASK-NNN-*.md` file
|
|
18
|
-
3. `docs/project/03-architecture.md` and `06-decisions.md` when relevant
|
|
19
|
-
|
|
20
|
-
## Git
|
|
21
|
-
|
|
22
|
-
- Branch: `task/*` from `vibeops task add`
|
|
23
|
-
- Commit messages include the TASK id, e.g. `feat(task-015): short title`
|
|
24
|
-
- Do not merge to main unless the human runs `vibeops task done` or asks
|
|
25
|
-
|
|
26
|
-
## Finishing
|
|
27
|
-
|
|
28
|
-
Fill **Result** and **Test Result** in the TASK file, then tell the human to run `vibeops task done TASK-NNN`.
|
|
3
|
+
- Work from `docs/tasks/TASK-NNN-*.md` only.
|
|
4
|
+
- One TASK at a time.
|
|
5
|
+
- Fill **Result** and **Test Result** before telling the human to run `vibeops task ship TASK-NNN`.
|
|
6
|
+
- Do not merge to integration or main unless the human runs `vibeops task merge` / `task release`.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Git safety for
|
|
2
|
+
description: Git safety for TASK branches
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Git safety
|
|
7
7
|
|
|
8
|
-
- Use branch pattern `task/*`
|
|
9
|
-
- Include
|
|
10
|
-
-
|
|
11
|
-
-
|
|
8
|
+
- Use branch pattern `task/*` from `vibeops task add`.
|
|
9
|
+
- Include TASK id in commits: `feat(task-nnn): …`.
|
|
10
|
+
- No force-push to shared branches.
|
|
11
|
+
- Run `vibeops task ship`, `task merge`, `task sync` when the human asks — do not merge locally in the IDE unless requested.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Update TASK Result and project memory before shipping
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Docs before task ship
|
|
7
|
+
|
|
8
|
+
Before `vibeops task ship`:
|
|
9
|
+
|
|
10
|
+
1. Fill **Result** and **Test Result** in the TASK file (paths, commands, outcomes — facts only).
|
|
11
|
+
2. If architecture or decisions changed, note them in the TASK; `task ship` can update `docs/project/*` via LLM.
|
|
12
|
+
|
|
13
|
+
Do not ship with placeholder `(not yet)` sections.
|
package/templates/core/AGENTS.md
CHANGED
|
@@ -1,44 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Project — AI Agent Guide
|
|
2
2
|
|
|
3
|
-
> Installed by
|
|
3
|
+
> Installed by VibeOps. Read before coding.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## TASK workflow
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
| Project memory | `docs/project/05-current-state.md`, `03-architecture.md`, `06-decisions.md` |
|
|
11
|
-
| Change history | Git commits on task branches |
|
|
12
|
-
| **Not** trusted | Chat history, Slack |
|
|
13
|
-
|
|
14
|
-
## Before you code
|
|
7
|
+
```bash
|
|
8
|
+
vibeops task add
|
|
9
|
+
# Plan / build in your agent (@docs/tasks/TASK-NNN-*.md)
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
vibeops task ship
|
|
12
|
+
vibeops task merge
|
|
13
|
+
vibeops task sync
|
|
14
|
+
```
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
Optional release to production: `vibeops task release`.
|
|
21
17
|
|
|
22
18
|
```bash
|
|
23
|
-
vibeops
|
|
24
|
-
vibeops task add # new TASK + branch
|
|
25
|
-
# Plan: refine TASK file with your agent (see client skills below)
|
|
26
|
-
# Build: implement per TASK Scope / Acceptance Criteria
|
|
27
|
-
vibeops task done # close TASK, update project docs, merge
|
|
28
|
-
vibeops status # briefing
|
|
29
|
-
vibeops llm connect # LLM for task add / task done
|
|
19
|
+
vibeops llm connect # optional — task add / task ship
|
|
30
20
|
```
|
|
31
21
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
| Tool | Rules / guide | Skills |
|
|
35
|
-
|------|----------------|--------|
|
|
36
|
-
| **Cursor** | `.cursor/rules/` | `.cursor/skills/` (`plan-task`, `implement-task`) |
|
|
37
|
-
| **Claude Code** | `CLAUDE.md` | `.claude/skills/` |
|
|
38
|
-
| **Codex CLI** | `AGENTS.md` (this file) | `.agents/skills/` |
|
|
39
|
-
|
|
40
|
-
## Rules (all agents)
|
|
22
|
+
## Rules
|
|
41
23
|
|
|
42
|
-
-
|
|
43
|
-
- **
|
|
44
|
-
-
|
|
24
|
+
- One TASK at a time; scope from `docs/tasks/TASK-NNN-*.md`.
|
|
25
|
+
- Fill **Result** and **Test Result** before `vibeops task ship`.
|
|
26
|
+
- Agents do not merge or sync unless the human asks.
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
## D-001 · VibeOps workflow
|
|
6
6
|
|
|
7
7
|
- **Date:** {{CREATED_AT}}
|
|
8
|
-
- **Decision:** TASK-driven development with `vibeops task add` / `task
|
|
8
|
+
- **Decision:** TASK-driven development with `vibeops task add` / `task ship` / `merge` / `sync` and Cursor for plan/build.
|
|
9
9
|
- **Why:** Git `docs/tasks` beats chat as source of truth.
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: implement-task
|
|
3
|
-
description:
|
|
4
|
-
disable-model-invocation: true
|
|
3
|
+
description: Implement the current VibeOps TASK file in the repo.
|
|
5
4
|
---
|
|
6
5
|
|
|
7
|
-
# Implement
|
|
6
|
+
# Implement TASK
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
1. Read the full `docs/tasks/TASK-NNN-*.md` (Scope, Acceptance Criteria, Test Plan).
|
|
9
|
+
2. Search the codebase before adding new patterns.
|
|
10
|
+
3. Implement only within the TASK scope.
|
|
11
|
+
4. Do not run `vibeops task ship`, `merge`, or `sync` — the human runs the CLI lifecycle.
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
2. `docs/project/05-current-state.md`, `03-architecture.md`, `06-decisions.md`
|
|
13
|
-
3. The TASK file referenced by the human
|
|
14
|
-
|
|
15
|
-
## Instructions
|
|
16
|
-
|
|
17
|
-
1. Work **only** within Scope and Acceptance Criteria.
|
|
18
|
-
2. Search the repo before adding files; match existing patterns.
|
|
19
|
-
3. Fill **Result** and **Test Result** with facts when done.
|
|
20
|
-
4. Do not run `vibeops task done` or merge — the human closes the TASK.
|
|
21
|
-
|
|
22
|
-
Tell the human to run `vibeops task done TASK-NNN` when ready.
|
|
13
|
+
When implementation is ready, tell the human to run `vibeops task ship TASK-NNN`.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Update TASK Result and project memory when finishing work
|
|
3
|
-
alwaysApply: true
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Docs when finishing a TASK
|
|
7
|
-
|
|
8
|
-
Before `vibeops task done`:
|
|
9
|
-
|
|
10
|
-
1. Fill **Result** and **Test Result** in the TASK file (paths, commands, outcomes — facts only).
|
|
11
|
-
2. If architecture or decisions changed, note them in the TASK; `task done` updates `docs/project/*`.
|
|
12
|
-
|
|
13
|
-
Do not mark a TASK complete with placeholder `(not yet)` sections.
|