gentle-pi 1.0.3 → 1.0.4
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/README.md +23 -5
- package/assets/migrations/managed-assets-v0.10.7.json +7 -0
- package/lib/native-review-cli.ts +5 -1
- package/lib/sdd-preflight.ts +9 -5
- package/package.json +1 -1
- package/scripts/verify-package-files.mjs +1 -0
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +132 -0
- package/tests/native-review-cli.test.ts +22 -0
- package/tests/package-manifest.test.ts +2 -2
- package/tests/sdd-preflight.test.ts +78 -0
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
**Turn Pi from a powerful coding agent into a controlled development harness.**
|
|
15
15
|
|
|
16
|
-
`gentle-pi` installs **el Gentleman** in Pi: a senior-architect operating layer for Spec-Driven Development, focused subagents, strict TDD evidence, reviewable work units, safety guards,
|
|
16
|
+
`gentle-pi` installs **el Gentleman** in Pi: a senior-architect operating layer for Spec-Driven Development, focused subagents, strict TDD evidence, reviewable work units, safety guards, project/user skill discovery, and bounded native review.
|
|
17
17
|
|
|
18
|
-
Pi already has strong tools. `gentle-pi` adds the discipline for using them well.
|
|
18
|
+
Pi already has strong tools. `gentle-pi` adds the discipline for using them well, then binds review and delivery decisions to Git-derived evidence instead of agent narration.
|
|
19
19
|
|
|
20
20
|
`gentle-pi` is the Pi-native package from the [Gentle-AI ecosystem](https://github.com/Gentleman-Programming/gentle-ai), built by [Gentleman Programming](https://github.com/Gentleman-Programming): the broader open-source project for turning AI coding agents into disciplined engineering environments with SDD workflows, skills, memory integrations, model routing, and review guardrails across multiple agents.
|
|
21
21
|
|
|
@@ -57,6 +57,8 @@ Most coding-agent sessions fail for operational reasons, not model reasons:
|
|
|
57
57
|
| **Skill discovery registry** | Maintains `.atl/skill-registry.md` from project and user skills so review/comment/PR workflows do not silently miss the right skill. |
|
|
58
58
|
| **Skill creation workflow** | Provides the `gentle-ai-skill-creator`/`gentle-ai-skill-improver` skills, `/skill-creation` prompt, and packaged style guide for LLM-first skills. |
|
|
59
59
|
| **Delivery skills** | Includes issue-first PRs, chained PRs, work-unit commits, cognitive docs, comment writing, and Judgment Day review. |
|
|
60
|
+
| **Bounded native review** | Freezes one candidate, dispatches only controller-selected lenses, records native authority, and reuses the same content-bound receipt at delivery gates. |
|
|
61
|
+
| **Verified native runtime** | Provisions the exact package-local Gentle AI v2.1.4 binary, verifies pinned archive/binary integrity, and rejects PATH, global, sibling, symlink, and mode fallbacks. |
|
|
60
62
|
| **Runtime safety** | Blocks destructive shell commands, asks for confirmation for sensitive operations, and blocks direct read/write/edit access to sensitive paths. |
|
|
61
63
|
|
|
62
64
|
## Install
|
|
@@ -107,6 +109,16 @@ Typical flow:
|
|
|
107
109
|
4. For a substantial change, ask Pi to use SDD. Natural-language requests are classified by the parent agent, not by brittle runtime regexes.
|
|
108
110
|
5. Review the phase artifacts instead of trusting floating chat context.
|
|
109
111
|
|
|
112
|
+
## Core workflow
|
|
113
|
+
|
|
114
|
+
1. **Install and inspect.** Install `gentle-pi`, open Pi in the target repository, then run `/gentle:status` or `/gentle:doctor`.
|
|
115
|
+
2. **Plan when risk justifies it.** Small work stays direct; substantial work uses SDD with Engram, OpenSpec, or both so requirements and decisions survive compaction.
|
|
116
|
+
3. **Build with evidence.** One focused writer implements the approved scope. When Strict TDD is available, apply and verify preserve RED → GREEN → TRIANGULATE → REFACTOR evidence.
|
|
117
|
+
4. **Review one candidate.** Native START derives and freezes the Git candidate, risk tier, selected lenses, authored-line budget, and correction allowance. Review actors assess that immutable view; they do not grant authority.
|
|
118
|
+
5. **Deliver the same candidate.** FINALIZE records native authority and an approved receipt. Commit, push, PR, and release gates validate that same receipt and live Git target with zero review actors; they never silently reopen review or reset its budget.
|
|
119
|
+
|
|
120
|
+
> **Trust what the system can derive, not what an agent claims.** Agents analyze the candidate. The package-local Gentle AI runtime owns scope, risk, findings, receipts, and lifecycle gates. This protects against accidental scope and identity drift, not a malicious same-user process that can replace local code or authority. See Gentle AI's [review authority threat model](https://github.com/Gentleman-Programming/gentle-ai/blob/main/docs/review-authority-threat-model.md) and [Chapter 21 — Verifiable Trust](https://the-amazing-gentleman-programming-book.vercel.app/en/book/Chapter21_Verifiable-Trust).
|
|
121
|
+
|
|
110
122
|
## How the harness decides what to do
|
|
111
123
|
|
|
112
124
|
`gentle-pi` routes through the smallest safe workflow:
|
|
@@ -128,7 +140,7 @@ The goal is not ceremony. The goal is to avoid accidental chaos. Once a task sto
|
|
|
128
140
|
| Reading 4+ files to understand a flow | Launch `scout`, `context-builder`, or the closest read-only mapping subagent. |
|
|
129
141
|
| Touching 2+ non-trivial code files | Delegate one writer; do not continue inline unless delegation is unavailable. |
|
|
130
142
|
| Commit, push, or PR after code changes | Validate the approved receipt and exact typed target with zero actors. |
|
|
131
|
-
| Wrong cwd, worktree/git accident, merge recovery, confusing test/env issue | Stop and
|
|
143
|
+
| Wrong cwd, worktree/git accident, merge recovery, confusing test/env issue | Stop, preserve the frozen scope, investigate separately, and validate the existing receipt; never launch a fresh review lens or reopen review as incident handling. |
|
|
132
144
|
| Long monolithic session with accumulating complexity, roughly 20 tool calls, 5 exploratory reads, or 2 non-mechanical edits | Pause and delegate the remaining work, or stop and explain the exact blocker. |
|
|
133
145
|
|
|
134
146
|
The intended balanced loop for a bounded bugfix is:
|
|
@@ -161,7 +173,7 @@ Pre-commit, pre-push, pre-PR, and release gates revalidate the recovered source,
|
|
|
161
173
|
| Security, permissions, data exposure/loss, architecture, dependencies | `review-risk` |
|
|
162
174
|
| Large PR, hot path, or >400 changed lines | Full 4R: `review-risk`, `review-resilience`, `review-readability`, `review-reliability` |
|
|
163
175
|
|
|
164
|
-
|
|
176
|
+
Risk selection is deterministic: documentation/comment/formatting-only changes use zero lenses; every other standard change uses exactly one dominant-risk lens; security/auth/update/payment paths, data-loss or exposure risk, shell/process integration, or more than 400 authored changed lines use the full 4R set. A standard review never accumulates multiple lenses ad hoc.
|
|
165
177
|
|
|
166
178
|
### Bounded review transactions
|
|
167
179
|
|
|
@@ -169,6 +181,8 @@ New ordinary review uses compact `gentle_review` `start -> finalize -> validate`
|
|
|
169
181
|
|
|
170
182
|
Native contract pairing is exact: this adapter supports `gentle-ai 2.1.4` only from its package-local verified binary and rechecks that version before every native operation. Production native operations resolve an absolute package-owned path and never fall back to `PATH` or a global executable. Once v2.1.4 has written review authority, rollback MUST preserve every native store and receipt and MUST NOT run a downgraded binary against that repository. Disable the Pi route or roll forward to a compatible authority-aware release instead; deleting authority data or reinstalling an older binary is not a rollback path.
|
|
171
183
|
|
|
184
|
+
Gentle AI v2.1.4 supports `gentle-ai review start --projection staged`, and upstream `main` now documents that focused-index workflow. The current `gentle-pi` adapter intentionally submits `projection: "workspace"` and does not expose staged projection yet. Native binary capability is not automatically a Pi adapter contract; package support still requires an adapter update, parity fixtures, and bounded validation.
|
|
185
|
+
|
|
172
186
|
### FINALIZE wrapper input
|
|
173
187
|
|
|
174
188
|
`gentle_review` accepts `input` as a JSON-serialized object string. For initial results, provide `review_result.lens_results[]`; each selected lens appears exactly once with `lens`, `findings`, and non-empty `evidence`. A clean lens uses `findings: []`. `final_evidence` and `final_verification_passed` are paired: provide both or neither.
|
|
@@ -558,7 +572,11 @@ Memory contract for SDD delegation:
|
|
|
558
572
|
|
|
559
573
|
| Path | Purpose |
|
|
560
574
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
|
561
|
-
| `extensions/gentle-ai.ts` | Injects identity,
|
|
575
|
+
| `extensions/gentle-ai.ts` | Injects identity, orchestrates native review authority and lifecycle gates, refreshes global SDD assets, registers commands, applies model/persona config, and enforces runtime safety. |
|
|
576
|
+
| `lib/native-review-cli.ts` | Strict package-local adapter for Gentle AI START, FINALIZE, VALIDATE, SDD binding, and status contracts. |
|
|
577
|
+
| `lib/review-candidate-view.ts` | Builds immutable changed-scope actor views while preserving full-tree, path, mode, symlink, and index integrity. |
|
|
578
|
+
| `lib/gentle-ai-binary.ts` | Resolves and verifies the confined package-local Gentle AI runtime without global or PATH fallback. |
|
|
579
|
+
| `scripts/gentle-ai-installer.mjs` | Downloads, verifies, extracts, and atomically promotes the pinned native runtime for six platform targets. |
|
|
562
580
|
| `extensions/startup-banner.ts` | Shows and configures the startup intro, color presets, compact runtime panel, and collaboration credit. |
|
|
563
581
|
| `extensions/sdd-init.ts` | Registers `/sdd-init` for OpenSpec initialization. |
|
|
564
582
|
| `extensions/skill-registry.ts` | Maintains `.atl/skill-registry.md` from project/user skills and closes file watchers on shutdown. |
|
package/lib/native-review-cli.ts
CHANGED
|
@@ -181,6 +181,10 @@ function stringValue(value: unknown): string { if (typeof value !== "string") th
|
|
|
181
181
|
function booleanValue(value: unknown): boolean { if (typeof value !== "boolean") throw new Error("expected boolean"); return value; }
|
|
182
182
|
function nonNegativeInteger(value: unknown): number { if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error("expected safe non-negative integer"); return value; }
|
|
183
183
|
function stringArray(value: unknown): readonly string[] { if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) throw new Error("expected string array"); return value; }
|
|
184
|
+
function decodeSelectedLenses(value: unknown, riskLevel: string, lensesRequired: boolean): readonly string[] {
|
|
185
|
+
if (value === null && riskLevel === "low" && !lensesRequired) return [];
|
|
186
|
+
return stringArray(value);
|
|
187
|
+
}
|
|
184
188
|
function enumString(value: unknown, allowed: readonly string[]): string { const parsed = stringValue(value); if (!allowed.includes(parsed)) throw new Error("unsupported enum"); return parsed; }
|
|
185
189
|
function parseJson(stdout: string, operation: NativeReviewOperation, mutating: boolean): Record<string, unknown> {
|
|
186
190
|
if (stdout.length === 0) throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.EMPTY_OUTPUT, operation, true, mutating, "native command returned empty output");
|
|
@@ -399,9 +403,9 @@ export class NativeReviewCliV214 {
|
|
|
399
403
|
const lineageId = requiredString(body.lineage_id);
|
|
400
404
|
if (request.lineageId && lineageId !== request.lineageId) throw nativeError(NATIVE_REVIEW_ERROR_CODE.IDENTITY_MISMATCH, NATIVE_REVIEW_OPERATION.START, true, "native start lineage mismatch");
|
|
401
405
|
const riskLevel = requiredString(body.risk_level);
|
|
402
|
-
const selectedLenses = stringArray(body.selected_lenses);
|
|
403
406
|
const action = enumString(body.action, NATIVE_START_ACTION_VALUES) as NativeStartAction;
|
|
404
407
|
const lensesRequired = booleanValue(body.lenses_required);
|
|
408
|
+
const selectedLenses = decodeSelectedLenses(body.selected_lenses, riskLevel, lensesRequired);
|
|
405
409
|
if (
|
|
406
410
|
!(NATIVE_RISK_LEVEL as readonly string[]).includes(riskLevel) ||
|
|
407
411
|
selectedLenses.some((lens) => !(NATIVE_REVIEW_LENS as readonly string[]).includes(lens)) ||
|
package/lib/sdd-preflight.ts
CHANGED
|
@@ -13,6 +13,7 @@ const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
|
13
13
|
const MANAGED_ASSETS_MANIFEST = "managed-assets.json";
|
|
14
14
|
const MANAGED_ASSETS_SCHEMA_VERSION = 1;
|
|
15
15
|
const LEGACY_MANAGED_ASSET_MANIFESTS = Object.freeze([
|
|
16
|
+
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.10.7.json"), version: "0.10.7" },
|
|
16
17
|
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.13.json"), version: "0.13.0" },
|
|
17
18
|
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.14.json"), version: "0.14.0" },
|
|
18
19
|
]);
|
|
@@ -318,7 +319,7 @@ function copyDirectoryFiles(
|
|
|
318
319
|
ownershipPrefix: string,
|
|
319
320
|
force: boolean,
|
|
320
321
|
manifest: ManagedAssetsManifest,
|
|
321
|
-
legacyAssetHashes: Readonly<Record<string, readonly string[]>> | undefined,
|
|
322
|
+
legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined,
|
|
322
323
|
): { copied: number; skipped: number } {
|
|
323
324
|
if (!existsSync(sourceDir)) return { copied: 0, skipped: 0 };
|
|
324
325
|
mkdirSync(targetDir, { recursive: true });
|
|
@@ -360,7 +361,7 @@ function copyDirectoryFiles(
|
|
|
360
361
|
? undefined
|
|
361
362
|
: managedAssetHash(installedContent);
|
|
362
363
|
if (managedHash === undefined) {
|
|
363
|
-
const legacyHashes = legacyAssetHashes?.[ownershipKey];
|
|
364
|
+
const legacyHashes = legacyAssetHashes?.()[ownershipKey];
|
|
364
365
|
const comparableLegacyHash = installedContent === undefined
|
|
365
366
|
? undefined
|
|
366
367
|
: managedAssetHash(
|
|
@@ -399,9 +400,12 @@ export function installSddAssets(
|
|
|
399
400
|
): { agents: number; chains: number; support: number; skipped: number } {
|
|
400
401
|
const agentHome = gentlePiAgentHome();
|
|
401
402
|
const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
: undefined;
|
|
403
|
+
let legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined;
|
|
404
|
+
if (force) {
|
|
405
|
+
let cachedLegacyAssetHashes: Record<string, readonly string[]> | undefined;
|
|
406
|
+
legacyAssetHashes = () =>
|
|
407
|
+
(cachedLegacyAssetHashes ??= readLegacyManagedAssetHashes());
|
|
408
|
+
}
|
|
405
409
|
const manifest = readManagedAssetsManifest(manifestPath);
|
|
406
410
|
const agents = copyDirectoryFiles(
|
|
407
411
|
join(ASSETS_DIR, "agents"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,7 @@ const requiredPaths = [
|
|
|
27
27
|
"assets/chains/sdd-full.chain.md",
|
|
28
28
|
"assets/chains/sdd-plan.chain.md",
|
|
29
29
|
"assets/chains/sdd-verify.chain.md",
|
|
30
|
+
"assets/migrations/managed-assets-v0.10.7.json",
|
|
30
31
|
"assets/migrations/managed-assets-v0.13.json",
|
|
31
32
|
"assets/migrations/managed-assets-v0.14.json",
|
|
32
33
|
"assets/support/sdd-status-contract.md",
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdd-apply
|
|
3
|
+
description: Implement SDD tasks with strict TDD evidence and review workload guard.
|
|
4
|
+
tools: read, grep, glob, edit, write, bash, mem_search, mem_get_observation, mem_save, mem_update
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the SDD apply executor for Gentle AI.
|
|
8
|
+
|
|
9
|
+
## Skill Resolution Contract
|
|
10
|
+
|
|
11
|
+
Use your assigned executor/phase skill for this SDD phase. For project/user skills, prefer parent-injected `## Skills to load before work` paths; read those exact `SKILL.md` files before work. Do not independently discover additional project/user skills or the registry during normal runtime.
|
|
12
|
+
|
|
13
|
+
If skill paths are missing, explicit fallback loading is allowed only as degraded self-healing. Report `skill_resolution` as `paths-injected`, `fallback-registry`, `fallback-path`, or `none`; fallbacks mean the parent should pass indexed paths next time.
|
|
14
|
+
|
|
15
|
+
## Memory Contract
|
|
16
|
+
|
|
17
|
+
Read your own input artifacts directly from the active backend before doing the phase work; do not wait for the parent to inline them. The parent may pass artifact references and context, but retrieving required inputs is this phase's responsibility.
|
|
18
|
+
|
|
19
|
+
Inputs to read (`engram`/`both`: `mem_search("<topic-key>")` then `mem_get_observation`; `openspec`: read the file under `openspec/changes/{change}/`):
|
|
20
|
+
- Tasks (required): `sdd/{change}/tasks`
|
|
21
|
+
- Spec (required): `sdd/{change}/spec`
|
|
22
|
+
- Design (required): `sdd/{change}/design`
|
|
23
|
+
- Previous apply-progress (if it exists): `sdd/{change}/apply-progress` — read and MERGE with your new progress; do NOT overwrite.
|
|
24
|
+
|
|
25
|
+
Persist this phase's artifact to the active backend before returning (mandatory):
|
|
26
|
+
- `engram`/`both`: call `mem_save` with title and `topic_key` `"sdd/{change}/apply-progress"`, `type: "architecture"`, `project` from context, and `capture_prompt: false` when the tool schema supports it (omit the field if an older schema rejects it).
|
|
27
|
+
- Also update the tasks artifact checkboxes via `mem_update` (`engram`/`both`) or file edit (`openspec`).
|
|
28
|
+
- `openspec`: write/update the apply-progress and tasks files under `openspec/changes/{change}/`.
|
|
29
|
+
- `none`: return progress inline.
|
|
30
|
+
|
|
31
|
+
Never claim persistence you did not perform.
|
|
32
|
+
|
|
33
|
+
## Status and Action Context Guard
|
|
34
|
+
|
|
35
|
+
Before writing code, consume structured SDD status from the parent prompt. If missing, produce the same fields using this lookup order: project override `.pi/gentle-ai/support/sdd-status-contract.md`, then globally installed `~/.pi/agent/gentle-ai/support/sdd-status-contract.md`, then the embedded status contract. Do not use `assets/support/...` as a runtime path; that is only the package source path before installation.
|
|
36
|
+
|
|
37
|
+
**Non-authoritative store carve-out:** when the native status JSON shows `nextRecommended: "resolve-via-engram"` (covers `artifactStore: engram`, `artifactStore: none`, and `artifactStore: both` without an `openspec/` directory), the status is non-authoritative. Do not treat `applyState`, `dependencies`, or `blockedReasons` from that status as real blockers. Resolve readiness as follows:
|
|
38
|
+
- `engram` (or `both` without openspec/): search Engram for `sdd/{change}/tasks`, `sdd/{change}/spec`, and `sdd/{change}/design` using `mem_search` + `mem_get_observation`. Proceed with implementation once those artifacts are confirmed present.
|
|
39
|
+
- `none`: there is no persistent backend. Return artifacts inline and ask the user to provide required inputs (tasks, spec, design) or acknowledge that no persistent artifact store is available.
|
|
40
|
+
|
|
41
|
+
Stop with `blocked` before editing if:
|
|
42
|
+
|
|
43
|
+
- active change selection is missing or ambiguous;
|
|
44
|
+
- `applyState: blocked` **and the status is authoritative** (openspec or both store);
|
|
45
|
+
- required apply artifacts are missing (confirmed by artifact store);
|
|
46
|
+
- `actionContext.mode: workspace-planning` and no `allowedEditRoots` are provided;
|
|
47
|
+
- any target file is outside the authoritative workspace or allowed edit roots.
|
|
48
|
+
|
|
49
|
+
If status says `applyState: all_done`, do not edit. Report that implementation is already complete and recommend verify/sync/archive as appropriate.
|
|
50
|
+
|
|
51
|
+
## Before Writing Code
|
|
52
|
+
|
|
53
|
+
Read structured status, proposal, specs, design, tasks, existing code, tests, `apply-progress.md` if present, and `openspec/config.yaml` when present.
|
|
54
|
+
|
|
55
|
+
## Review Workload Gate
|
|
56
|
+
|
|
57
|
+
Before implementing, inspect `tasks.md` for `Review Workload Forecast` and these guard lines:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
Decision needed before apply: Yes|No
|
|
61
|
+
Chained PRs recommended: Yes|No
|
|
62
|
+
Chain strategy: stacked-to-main|feature-branch-chain|size-exception|pending
|
|
63
|
+
400-line budget risk: Low|Medium|High
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If any of these are true:
|
|
67
|
+
|
|
68
|
+
- `Decision needed before apply: Yes`
|
|
69
|
+
- `Chained PRs recommended: Yes`
|
|
70
|
+
- `400-line budget risk: High`
|
|
71
|
+
|
|
72
|
+
then continue only when the parent prompt gives a resolved delivery path:
|
|
73
|
+
|
|
74
|
+
- `auto-chain` or chosen chained/stacked PR mode: implement only the assigned work-unit slice and report the PR boundary.
|
|
75
|
+
- `exception-ok` or `size:exception`: continue only if the prompt explicitly says the maintainer accepts the exception.
|
|
76
|
+
- `single-pr` above budget: continue only after explicit `size:exception` approval.
|
|
77
|
+
|
|
78
|
+
If no delivery decision is provided, STOP before writing code and return `blocked` with the exact decision needed.
|
|
79
|
+
|
|
80
|
+
## Strict TDD Gate
|
|
81
|
+
|
|
82
|
+
If `openspec/config.yaml` declares strict TDD and a test runner, or the parent prompt says strict TDD is active:
|
|
83
|
+
|
|
84
|
+
1. Read the global Gentle AI strict-TDD support guidance when available. If a project-local `.pi/gentle-ai/support/strict-tdd.md` exists, treat it as an override.
|
|
85
|
+
2. Follow RED → GREEN → TRIANGULATE → REFACTOR for every assigned task.
|
|
86
|
+
3. Do not write production code before a failing test or equivalent RED test is written.
|
|
87
|
+
4. Run relevant focused tests during GREEN and after refactors.
|
|
88
|
+
5. Write a `TDD Cycle Evidence` table in `apply-progress.md`.
|
|
89
|
+
|
|
90
|
+
If strict TDD is active and no external support file is available, follow the RED/GREEN/TRIANGULATE/REFACTOR contract from this prompt. Do not silently fall back to standard mode.
|
|
91
|
+
|
|
92
|
+
## Persisted Task Checkbox Contract
|
|
93
|
+
|
|
94
|
+
`sdd-apply` owns persisted task completion. In all modes, including strict TDD, mark each completed implementation task in the persisted tasks artifact immediately after completion:
|
|
95
|
+
|
|
96
|
+
- `openspec` / `both`: update `openspec/changes/{change}/tasks.md` from `- [ ]` to `- [x]` for completed tasks.
|
|
97
|
+
- `engram`: update the `sdd/{change}/tasks` observation when memory tools are explicitly available.
|
|
98
|
+
- `none`: report task progress inline and state that no persisted task artifact was updated.
|
|
99
|
+
|
|
100
|
+
Internal todos and `apply-progress.md` are not enough completion evidence.
|
|
101
|
+
|
|
102
|
+
Before returning, re-read the persisted tasks artifact and confirm every task you report as completed is visibly marked `- [x]`. If the artifact still shows a completed task as `- [ ]`, fix the checkbox before returning or return `blocked` explaining why it cannot be reconciled. Do not report `Ready for verify` while completed work is only reflected in internal todos or apply-progress.
|
|
103
|
+
|
|
104
|
+
## Standard Mode
|
|
105
|
+
|
|
106
|
+
If strict TDD is not active, implement assigned tasks against specs and design, update persisted task checkboxes as work completes, and record verification evidence.
|
|
107
|
+
|
|
108
|
+
## Apply Progress
|
|
109
|
+
|
|
110
|
+
Update `openspec/changes/{change}/apply-progress.md` cumulatively. If previous progress exists, merge it with new progress; never overwrite completed work.
|
|
111
|
+
|
|
112
|
+
Include:
|
|
113
|
+
|
|
114
|
+
- completed tasks and the matching persisted task checkbox updates;
|
|
115
|
+
- files changed;
|
|
116
|
+
- test commands run;
|
|
117
|
+
- TDD evidence when strict TDD is active;
|
|
118
|
+
- deviations from design;
|
|
119
|
+
- remaining tasks, including exact unchecked `- [ ]` lines when any remain;
|
|
120
|
+
- workload / PR boundary;
|
|
121
|
+
- structured status consumed or produced, including `actionContext` warnings.
|
|
122
|
+
|
|
123
|
+
Do NOT launch child subagents. Parent/orchestrator owns delegation. Never commit unless the user explicitly asks.
|
|
124
|
+
|
|
125
|
+
Rules:
|
|
126
|
+
|
|
127
|
+
- ALWAYS consume or produce structured status before implementation; do not infer readiness from conversation alone.
|
|
128
|
+
- STOP on unsafe `actionContext` or edit roots.
|
|
129
|
+
- Mark completed tasks in the persisted tasks artifact as you go, not only at the end.
|
|
130
|
+
- Before returning, re-read the persisted tasks artifact and ensure completed tasks are visibly marked `- [x]`; internal todos are not completion evidence.
|
|
131
|
+
|
|
132
|
+
Return the standard phase envelope with status, executive_summary, artifacts, next_recommended, risks, and skill_resolution.
|
|
@@ -72,6 +72,28 @@ test("native client re-verifies the pinned version before every operation and us
|
|
|
72
72
|
assert.equal(queue.calls.every((call) => call.cwd === "/repo with spaces"), true);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
test("native START normalizes null selected lenses only for low-risk no-lens responses", async () => {
|
|
76
|
+
const start = JSON.parse(START.stdout) as Record<string, unknown>;
|
|
77
|
+
const valid = queuedAdapter([VERSION, {
|
|
78
|
+
stdout: JSON.stringify({
|
|
79
|
+
...start,
|
|
80
|
+
risk_level: "low",
|
|
81
|
+
selected_lenses: null,
|
|
82
|
+
lenses_required: false,
|
|
83
|
+
}),
|
|
84
|
+
}]);
|
|
85
|
+
assert.deepEqual((await new NativeReviewCliV213(valid.adapter).start({ cwd: "/repo" })).selectedLenses, []);
|
|
86
|
+
|
|
87
|
+
for (const scenario of [
|
|
88
|
+
{ risk_level: "low", lenses_required: true },
|
|
89
|
+
{ risk_level: "medium", lenses_required: false },
|
|
90
|
+
{ risk_level: "high", lenses_required: false },
|
|
91
|
+
]) {
|
|
92
|
+
const queue = queuedAdapter([VERSION, { stdout: JSON.stringify({ ...start, ...scenario, selected_lenses: null }) }]);
|
|
93
|
+
await assert.rejects(() => new NativeReviewCliV213(queue.adapter).start({ cwd: "/repo" }), NativeReviewCliError);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
75
97
|
test("native START action/lenses_required matrix accepts only authoritative dispatch combinations", async () => {
|
|
76
98
|
const start = JSON.parse(START.stdout) as Record<string, unknown>;
|
|
77
99
|
const valid = [
|
|
@@ -1015,9 +1015,9 @@ test("pi-pretty wrapper uses real package path resolution for pnpm symlink insta
|
|
|
1015
1015
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1016
1016
|
});
|
|
1017
1017
|
|
|
1018
|
-
test("v1.0.
|
|
1018
|
+
test("v1.0.4 release package and runtime stop before delivery or publication", () => {
|
|
1019
1019
|
const packageJson = readPackageJson();
|
|
1020
|
-
assert.equal(packageJson.version, "1.0.
|
|
1020
|
+
assert.equal(packageJson.version, "1.0.4", "the release manifest must remain explicitly pinned to v1.0.4");
|
|
1021
1021
|
assert.equal(
|
|
1022
1022
|
packageJson.scripts?.test,
|
|
1023
1023
|
"node --experimental-strip-types --test tests/*.test.ts && pnpm run test:harness",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import {
|
|
3
4
|
existsSync,
|
|
4
5
|
mkdtempSync,
|
|
@@ -120,6 +121,83 @@ test("writeSddPreflightToDisk is non-fatal when directory is not writable (no th
|
|
|
120
121
|
});
|
|
121
122
|
});
|
|
122
123
|
|
|
124
|
+
test("forced asset refresh migrates the exact v0.10.7 malformed sdd-apply asset and preserves user edits", () => {
|
|
125
|
+
const packageRoot = join(import.meta.dirname, "..");
|
|
126
|
+
const legacySource = readFileSync(
|
|
127
|
+
join(
|
|
128
|
+
packageRoot,
|
|
129
|
+
"tests",
|
|
130
|
+
"fixtures",
|
|
131
|
+
"v0.10.7",
|
|
132
|
+
"assets",
|
|
133
|
+
"agents",
|
|
134
|
+
"sdd-apply.md",
|
|
135
|
+
),
|
|
136
|
+
"utf8",
|
|
137
|
+
);
|
|
138
|
+
const currentSource = readFileSync(
|
|
139
|
+
join(packageRoot, "assets", "agents", "sdd-apply.md"),
|
|
140
|
+
"utf8",
|
|
141
|
+
);
|
|
142
|
+
const temporaryAgentHome = mkdtempSync(join(tmpdir(), "gentle-pi-v0107-preflight-"));
|
|
143
|
+
const temporaryUserAgentHome = mkdtempSync(join(tmpdir(), "gentle-pi-v0107-user-preflight-"));
|
|
144
|
+
const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
|
|
145
|
+
const installed = join(temporaryAgentHome, "agents", "sdd-apply.md");
|
|
146
|
+
const userInstalled = join(temporaryUserAgentHome, "agents", "sdd-apply.md");
|
|
147
|
+
const userEdited = legacySource.replace(
|
|
148
|
+
"You are the SDD apply executor for Gentle AI.",
|
|
149
|
+
"You are the user-customized SDD apply executor for Gentle AI.",
|
|
150
|
+
);
|
|
151
|
+
try {
|
|
152
|
+
process.env.GENTLE_PI_AGENT_HOME = temporaryAgentHome;
|
|
153
|
+
mkdirSync(join(temporaryAgentHome, "agents"), { recursive: true });
|
|
154
|
+
writeFileSync(installed, legacySource);
|
|
155
|
+
mkdirSync(join(temporaryAgentHome, "gentle-ai"), { recursive: true });
|
|
156
|
+
writeFileSync(
|
|
157
|
+
join(temporaryAgentHome, "gentle-ai", "managed-assets.json"),
|
|
158
|
+
JSON.stringify({ schemaVersion: 1, assets: {} }),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
installSddAssets(packageRoot, true);
|
|
162
|
+
|
|
163
|
+
assert.equal(readFileSync(installed, "utf8"), currentSource);
|
|
164
|
+
assert.match(readFileSync(installed, "utf8"), /^tools:\n - read$/m);
|
|
165
|
+
const managedAssets = JSON.parse(
|
|
166
|
+
readFileSync(
|
|
167
|
+
join(temporaryAgentHome, "gentle-ai", "managed-assets.json"),
|
|
168
|
+
"utf8",
|
|
169
|
+
),
|
|
170
|
+
) as { assets: Record<string, string> };
|
|
171
|
+
assert.equal(
|
|
172
|
+
managedAssets.assets["agents/sdd-apply.md"],
|
|
173
|
+
createHash("sha256").update(currentSource).digest("hex"),
|
|
174
|
+
"the migrated asset must record current package ownership",
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
installSddAssets(packageRoot, true);
|
|
178
|
+
assert.equal(
|
|
179
|
+
readFileSync(installed, "utf8"),
|
|
180
|
+
currentSource,
|
|
181
|
+
"a current package-managed asset must remain refreshable",
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
process.env.GENTLE_PI_AGENT_HOME = temporaryUserAgentHome;
|
|
185
|
+
mkdirSync(join(temporaryUserAgentHome, "agents"), { recursive: true });
|
|
186
|
+
writeFileSync(userInstalled, userEdited);
|
|
187
|
+
installSddAssets(packageRoot, true);
|
|
188
|
+
assert.equal(
|
|
189
|
+
readFileSync(userInstalled, "utf8"),
|
|
190
|
+
userEdited,
|
|
191
|
+
"a user-edited variant of the malformed legacy asset must remain untouched",
|
|
192
|
+
);
|
|
193
|
+
} finally {
|
|
194
|
+
if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
|
|
195
|
+
else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
|
|
196
|
+
rmSync(temporaryAgentHome, { recursive: true, force: true });
|
|
197
|
+
rmSync(temporaryUserAgentHome, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
123
201
|
test("forced asset refresh migrates only untouched v0.14 package contracts and preserves user edits", () => {
|
|
124
202
|
const packageRoot = join(import.meta.dirname, "..");
|
|
125
203
|
const fixture = readFileSync(
|