Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .add/milestones/build-strategy-facets/MILESTONE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
goal: the build phase carries a structured, domain-anchored implementation strategy — algorithm approach, data strategy, dev pattern, and optimization stance are declared facets (not one overloaded line), each anchored upstream (§0/§1/§3), harvested per-facet into the §7 Decisions (ADR) block, and cross-cited by §7 Watch
rationale: intake bucket=one-task-gap → micro-milestone (2026-07-07): senior review of TASK.md.tmpl found §5's single `Strategy (ordered batches)` line conflates build order · architecture pattern · issue advice · persona stance, so builds skip the domain implementation decision (algorithm/data/pattern/optimization); no active milestone's goal covers method-template structure
stage: mvp · status: active · created: 2026-07-07T03:57:31+00:00
release: pending
release: 1.18.0

> SDD living doc for this milestone. Keep it THIN: breadth, shared decisions, and
> exit criteria only — per-task detail lives in each `.add/tasks/<slug>/TASK.md`,
Expand Down
2 changes: 1 addition & 1 deletion .add/milestones/delta-drain/MILESTONE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
goal: the 4 open SPEC deltas are each resolved into a shipped behavior — compact-foundation gains a read-only --propose preview, personas gain a dedicated `verify` flow value, the streams.md worker-contract `<persona>` block names the flow preference, and status/check render an engine-built persona roster line — leaving zero open SPEC deltas
rationale: intake bucket=sub-milestone (2026-07-07, Tin: "implement all directly"): 4 independent deltas from 3 prior tasks, each small but crossing engine+skill+guard surfaces; loose fast tasks would lose the shared roster/flow decisions, so a thin milestone binds them
stage: mvp · status: active · created: 2026-07-07T08:15:15+00:00
release: pending
release: 1.18.0

> SDD living doc for this milestone. Keep it THIN: breadth, shared decisions, and
> exit criteria only — per-task detail lives in each `.add/tasks/<slug>/TASK.md`,
Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog

## 1.17.0 — 2026-07-07
## 1.18.0 — 2026-07-07

- Drain the 4 open SPEC deltas — 0 carried · 0 key decision(s)
- Faceted §5 build strategy — 0 carried · 0 key decision(s)

## 1.17.0 — 2026-07-06

- Persona domain-fit nudge — 2 carried · 0 key decision(s)
- Method ergonomics — 0 carried · 0 key decision(s)
Expand Down
7 changes: 7 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Releases

## 1.18.0 — 2026-07-07
milestones: delta-drain, build-strategy-facets
loose tasks: none
waivers: reclaim-ticket-race, js-reclaim-lock-heartbeat
actor: Tin Dang <tindang.ht97@gmail.com> (git)
evidence: test_release_1_18_0.py 11/11 green · add.py check green · milestones build-strategy-facets(#139) + delta-drain(#140) merged to main

## 1.17.0 — 2026-07-07
milestones: persona-domain-fit, method-ergonomics, dynamic-personas, self-improving-loop
loose tasks: grep-binary-agnostic-milestone-test, prune-data-update-lock, sweep-orphan-reclaim-tickets, adr-harvester-multiline-fields, strip-scaffold-backtick-comment-fix, worktree-isolated-spawn-default, fold-glossary-deltas, reclaim-ticket-race, js-reclaim-lock-heartbeat, scope-components-check, fastlane-intake-nudge, persona-required-domain-hint
Expand Down
2 changes: 1 addition & 1 deletion add-method/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "add",
"displayName": "ADD — AI-Driven Development",
"version": "1.17.0",
"version": "1.18.0",
"description": "ADD (AI-Driven Development). One skill. Eight steps. Five disciplines. Every feature ships through the loop — a minimal, state-tracked Claude Code skill that ships the AIDD book as its trust layer.",
"author": {
"name": "Tin Dang",
Expand Down
31 changes: 31 additions & 0 deletions add-method/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,37 @@ All notable changes to the ADD method (`@pilotspace/add` on npm,
`pilotspace-add` on PyPI) are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/); versions follow semver.

## [1.18.0] — 2026-07-07

Minor: two milestones — **build-strategy-facets** (§5 build strategy becomes
four domain-generic facet lines with fast-lane collapse, plus per-facet ADR
harvest) and **delta-drain** (all 4 open SPEC deltas resolved into shipped
behavior, leaving zero open SPEC deltas). All additive; no gate weakened,
nothing removed or renamed.

### Added
- **Faceted §5 build strategy** — four domain-generic facet lines drafted at
the tests→build cross: `Approach` (domain strategy) · `Data strategy` ·
`Pattern` · `Optimization stance`; the fast lane collapses them to one line
(build-strategy-facets).
- **per-facet ADR harvest** — each §5 facet lands in §7 Decisions (ADR) as its
own actor-tagged line at done (build-strategy-facets).
- **compact-foundation `--propose`** — a read-only preview of the compaction a
run would take; inspect before any byte moves (delta-drain).
- **`verify` flow value for personas** — personas route to the verify surface
directly; the streams.md worker-contract `<persona>` block names the flow
preference (delta-drain).
- **persona roster line** — `status`/`check` render an engine-built roster with
flows, never hand-maintained (delta-drain).

### Changed
- §5 build strategy guidance, phase guides, and the TASK.md/TASK.fast.md
templates teach and carry the facet block (build-strategy-facets).

### Disclosed waivers (non-security, signed)
- `reclaim-ticket-race` — lock-reclaim TOCTOU flake; owner Tin Dang, expires 2026-08-04.
- `js-reclaim-lock-heartbeat` — JS lock-heartbeat race; owner Tin Dang, expires 2026-08-04.

## [1.17.0] — 2026-07-06

Minor: four milestones — **method-ergonomics** (every recurring gate rule becomes
Expand Down
29 changes: 28 additions & 1 deletion add-method/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
<a href="https://www.npmjs.com/package/@pilotspace/add"><img alt="npm version" src="https://img.shields.io/npm/v/@pilotspace/add.svg"></a>
<a href="https://pypi.org/project/pilotspace-add/"><img alt="PyPI version" src="https://img.shields.io/pypi/v/pilotspace-add.svg"></a>
<a href="https://github.com/pilotspace/ADD/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg"></a>
<a href="https://pilotspace.github.io/ADD/"><img alt="Read the book" src="https://img.shields.io/badge/docs-read%20the%20book-blue.svg"></a>
<a href="https://github.com/pilotspace/ADD/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pilotspace/ADD.svg"></a>
</p>

# ADD — AI-Driven Development

**One skill. Eight steps. Five disciplines. Every feature ships through the loop.**
**Describe the feature. The agent drives the build. You approve once — exactly where a mistake would actually cost you.**

> A minimal, state-tracked skill for building software when the AI writes the code
> and **you** own the two things it cannot do alone: decide *what* to build, and
Expand Down Expand Up @@ -46,8 +47,28 @@ scenarios → contract → tests as one bundle (you approve once, at the frozen
contract), then builds and verifies to green. Full detail below — **Install**,
**Use it**, and the [10-minute Quickstart](./GETTING-STARTED.md).

## Highlights

- ✅ **Approve once, then let it run** — one human sign-off at the frozen contract; the agent builds the rest.
- 🔬 **Proof, not promises** — verified against observed behavior and pre-declared expectations, never just a plausible-looking diff.
- 🔒 **Security never gets waved through** — any security finding is a hard stop, human in the loop.
- 🌱 **Prototype to production** — task → milestone → graduate (analytics-gated) → recorded release, one method throughout.
- 🧠 **Smarter as you go** — lessons consolidate into a living, compacting foundation carried across milestones.
- 🎨 **See it before you build it** — a wireframe and a zero-dependency HTML mock, approved before any code.
- 👥 **Built for teams** — git-native multi-user, N parallel milestones, DAG-scheduled waves.
- 🧩 **One slice, many components** — monorepo or multi-repo, in one team.
- 🤝 **Works with your AI** — Claude, Copilot, Cursor, Codex, Gemini; install via npm, pip, or the Claude Code plugin.

> _Direction before speed. Trust comes from passing tests — not from reading code and finding it plausible._

## Why ADD (and why it is minimal)

Every AI coding tool can write code fast now. The part that never got solved is
trust — how do you know it built the *right* thing, and how do you know it's
*correct*, without reading every line yourself? ADD answers both: freeze the
direction *before* any code is written, then trust the result through passing
evidence, never a diff that merely looks right.

Heavy doc-first methods burn your time writing documents and lose the thread
across sessions (context rot). ADD fixes both:

Expand Down Expand Up @@ -225,6 +246,12 @@ be upset to lose is "the code," you're still working the old way.
Start at [`docs/README.md`](./docs/README.md) — Foundations → the six steps →
operating it across a team → templates, prompts, and a full worked example.

More entry points:

- 📖 [Read the book online](https://pilotspace.github.io/ADD/) — the full AIDD method, chapter by chapter
- 🔍 [Full hands-on walkthrough](./GETTING-STARTED.md) — one real feature, end to end
- 🗞️ [ADD Across the Org: AI-Driven Development Beyond Code](https://inkpaper-blog.pages.dev/series/add-across-the-org/)

## What's next

**Dynamic Agent Skills** — the next scope: skills that adapt at runtime to the
Expand Down
4 changes: 2 additions & 2 deletions add-method/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion add-method/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pilotspace/add",
"version": "1.17.0",
"version": "1.18.0",
"description": "ADD (AI-Driven Development). One skill. Eight steps. Five disciplines. Every feature ships through the loop — a minimal, state-tracked Claude Code skill that ships the AIDD book as its trust layer.",
"bin": {
"add": "bin/cli.js"
Expand Down
2 changes: 1 addition & 1 deletion add-method/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pilotspace-add"
version = "1.17.0"
version = "1.18.0"
description = "ADD (AI-Driven Development). One skill. Eight steps. Five disciplines. Every feature ships through the loop — install the ADD skill, tooling, and AIDD book into any project."
readme = "README.md"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion add-method/src/add_method/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
from add_method._installer import install

__all__ = ["install"]
__version__ = "1.17.0"
__version__ = "1.18.0"
25 changes: 5 additions & 20 deletions add-method/tooling/test_release_1_17_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,26 +95,11 @@ def test_audit_line_survives_bumps(self):


class ReleaseShapeTest(unittest.TestCase):
def test_versions_agree_at_1_17_0(self):
pkg = json.loads((PKG / "package.json").read_text(encoding="utf-8"))["version"]
py = re.search(r'(?m)^version\s*=\s*"([^"]+)"',
(PKG / "pyproject.toml").read_text(encoding="utf-8")).group(1)
self.assertEqual((pkg, py), (VERSION, VERSION),
"publish.yml's guard would fail this release closed")

def test_plugin_version_agrees(self):
plugin = json.loads(
(PKG / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")
)["version"]
self.assertEqual(plugin, VERSION,
"the Claude Code plugin manifest must match the shipped version")

def test_runtime_version_agrees(self):
init = (PKG / "src" / "add_method" / "__init__.py").read_text(encoding="utf-8")
runtime = re.search(r'(?m)^__version__\s*=\s*"([^"]+)"', init).group(1)
self.assertEqual(runtime, VERSION,
"add_method.__version__ must match the shipped version")

# NOTE: 1.17.0 is superseded by 1.18.0 — the live-version-agreement assertions
# (versions/plugin/runtime == VERSION) moved to test_release_1_18_0.py (the
# forward-migration the release-gate pattern demands; the 1.16.0 -> 1.16.1
# precedent). This file keeps only lineage + shipped-doc + parity checks,
# which stay true across bumps.
def test_getting_started_mentions_guide_line(self):
text = (PKG / "GETTING-STARTED.md").read_text(encoding="utf-8")
self.assertIn("guide :", text,
Expand Down
127 changes: 127 additions & 0 deletions add-method/tooling/test_release_1_18_0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""Red/green tests for the 1.18.0 release readiness (build-strategy-facets + delta-drain).

Minor cut: two milestones — **build-strategy-facets** (§5 build strategy becomes
four domain-generic facet lines — Approach · Data strategy · Pattern ·
Optimization stance — with fast-lane collapse, plus per-facet ADR harvest into
§7) and **delta-drain** (the 4 open SPEC deltas each resolved into shipped
behavior: `compact-foundation --propose` read-only preview · a dedicated
`verify` persona flow value · the streams.md worker-contract `<persona>` block
names the flow preference · an engine-built persona roster line in
status/check). All additive; no gate weakened, nothing removed or renamed.

In-repo readiness only — the live-registry halves (npm/PyPI serving 1.18.0) are
verify-gate EVIDENCE gathered after the human-gated tag push, never unit tests.
Run:
python3 -m unittest test_release_1_18_0 -v
"""
import hashlib
import json
import re
import unittest
from pathlib import Path

HERE = Path(__file__).resolve().parent
PKG = HERE.parent # add-method/
REPO = PKG.parent
BUNDLE = PKG / "src" / "add_method" / "_bundled"

CHANGELOG = PKG / "CHANGELOG.md"
CI_YML = REPO / ".github" / "workflows" / "ci.yml"
PUBLISH_YML = REPO / ".github" / "workflows" / "publish.yml"

VERSION = "1.18.0"
PRIOR_VERSIONS = ("1.17.0", "1.16.1", "1.16.0", "1.15.0", "1.14.0", "1.13.0", "1.12.0",
"1.11.0", "1.10.0", "1.9.0", "1.8.0", "1.7.3", "1.7.2", "1.7.1",
"1.7.0", "1.6.0", "1.5.0", "1.4.0", "1.3.0", "1.2.0", "1.1.0", "1.0.0")
from engine_pin import ENGINE_MD5
CANONICAL_AUDIT = "run: python3 .add/tooling/add.py audit"
# the headline changes the 1.18.0 notes must name
FEATURE_ANCHORS = ("Approach", "Data strategy", "Optimization stance",
"per-facet ADR harvest", "compact-foundation `--propose`",
"`verify` flow", "persona roster line")


class ChangelogTest(unittest.TestCase):
def test_changelog_has_1_18_0_entry(self):
self.assertTrue(CHANGELOG.is_file(), "CHANGELOG.md missing")
text = CHANGELOG.read_text(encoding="utf-8")
self.assertIn(f"## [{VERSION}]", text)
for prior in PRIOR_VERSIONS:
self.assertIn(f"## [{prior}]", text,
f"the {prior} lineage entry must survive the bump")
entry = text.split(f"## [{VERSION}]", 1)[1].split("## [", 1)[0]
for anchor in FEATURE_ANCHORS:
self.assertIn(anchor, entry, f"1.18.0 entry must name: {anchor}")

def test_changelog_ships_in_both_channels(self):
files = json.loads((PKG / "package.json").read_text(encoding="utf-8"))["files"]
self.assertIn("CHANGELOG.md", files, "npm tarball must ship the changelog")
self.assertIn("include CHANGELOG.md",
(PKG / "MANIFEST.in").read_text(encoding="utf-8"),
"sdist/wheel must ship the changelog")


class SecurityPolicyShipsTest(unittest.TestCase):
"""SECURITY.md must keep travelling with both published distributions."""

def test_security_md_present(self):
self.assertTrue((PKG / "SECURITY.md").is_file(),
"add-method/SECURITY.md must exist")

def test_security_md_ships_in_both_channels(self):
files = json.loads((PKG / "package.json").read_text(encoding="utf-8"))["files"]
self.assertIn("SECURITY.md", files, "npm tarball must ship SECURITY.md")
self.assertIn("include SECURITY.md",
(PKG / "MANIFEST.in").read_text(encoding="utf-8"),
"sdist/wheel must ship SECURITY.md")


class WorkflowHygieneTest(unittest.TestCase):
def test_no_deprecated_actions(self):
for wf in (CI_YML, PUBLISH_YML):
text = wf.read_text(encoding="utf-8")
self.assertNotIn("actions/checkout@v4", text, wf.name)
self.assertNotIn("actions/setup-python@v5", text, wf.name)
self.assertNotIn("actions/setup-node@v4", text, wf.name)

def test_audit_line_survives_bumps(self):
self.assertIn(CANONICAL_AUDIT, CI_YML.read_text(encoding="utf-8"),
"the seam-audit command must stay byte-identical")


class ReleaseShapeTest(unittest.TestCase):
def test_versions_agree_at_1_18_0(self):
pkg = json.loads((PKG / "package.json").read_text(encoding="utf-8"))["version"]
py = re.search(r'(?m)^version\s*=\s*"([^"]+)"',
(PKG / "pyproject.toml").read_text(encoding="utf-8")).group(1)
self.assertEqual((pkg, py), (VERSION, VERSION),
"publish.yml's guard would fail this release closed")

def test_plugin_version_agrees(self):
plugin = json.loads(
(PKG / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")
)["version"]
self.assertEqual(plugin, VERSION,
"the Claude Code plugin manifest must match the shipped version")

def test_runtime_version_agrees(self):
init = (PKG / "src" / "add_method" / "__init__.py").read_text(encoding="utf-8")
runtime = re.search(r'(?m)^__version__\s*=\s*"([^"]+)"', init).group(1)
self.assertEqual(runtime, VERSION,
"add_method.__version__ must match the shipped version")

def test_getting_started_mentions_guide_line(self):
text = (PKG / "GETTING-STARTED.md").read_text(encoding="utf-8")
self.assertIn("guide :", text,
"orient docs must name the phase-playbook line")

def test_engine_trees_parity(self):
for p in (HERE / "add.py", REPO / ".add" / "tooling" / "add.py",
BUNDLE / "tooling" / "add.py"):
self.assertEqual(hashlib.md5(p.read_bytes()).hexdigest(), ENGINE_MD5,
f"engine trees must stay byte-identical + pinned: {p}")


if __name__ == "__main__":
unittest.main(verbosity=2)