openspecpm 0.1.0-alpha.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +148 -86
- package/README.md +388 -352
- package/cli/bin/openspecpm.js +218 -198
- package/cli/src/adapters/azure.js +21 -5
- package/cli/src/adapters/gitlab.js +10 -5
- package/cli/src/audit.js +39 -7
- package/cli/src/bdd/judge.js +216 -0
- package/cli/src/commands/bulk.js +10 -0
- package/cli/src/commands/doctor.js +11 -0
- package/cli/src/commands/propose.js +41 -6
- package/cli/src/commands/reconcile.js +17 -4
- package/cli/src/commands/sync.js +70 -5
- package/cli/src/commands/validate.js +32 -1
- package/cli/src/http.js +14 -2
- package/cli/src/notify.js +25 -2
- package/cli/src/openspec-bridge.js +31 -0
- package/cli/src/tracking.js +30 -5
- package/package.json +2 -1
- package/skill/openspecpm/SKILL.md +74 -74
- package/skill/openspecpm/references/conventions.md +106 -105
- package/skill/openspecpm/references/execute.md +4 -4
- package/skill/openspecpm/references/plan.md +2 -2
- package/skill/openspecpm/references/structure.md +52 -52
- package/skill/openspecpm/references/sync.md +56 -56
package/cli/src/notify.js
CHANGED
|
@@ -24,19 +24,42 @@ export async function notify({ config, title, body, level = 'info', fetchImpl =
|
|
|
24
24
|
const errors = [];
|
|
25
25
|
for (const t of targets) {
|
|
26
26
|
try {
|
|
27
|
-
await fetchImpl(t.url, {
|
|
27
|
+
const res = await fetchImpl(t.url, {
|
|
28
28
|
method: 'POST',
|
|
29
29
|
headers: { 'Content-Type': 'application/json' },
|
|
30
30
|
body: JSON.stringify(formatPayload(t.kind, { title, body, level })),
|
|
31
31
|
});
|
|
32
|
+
// A 401/403/500 from Slack/Teams reaches the network but the message
|
|
33
|
+
// never lands. Without this check, those would be counted as `sent`,
|
|
34
|
+
// and a user running `standup --broadcast` would think their channel
|
|
35
|
+
// got the update when the webhook silently rejected it.
|
|
36
|
+
if (!res?.ok) {
|
|
37
|
+
const status = res?.status ?? '?';
|
|
38
|
+
const statusText = res?.statusText ?? '';
|
|
39
|
+
let snippet = '';
|
|
40
|
+
try {
|
|
41
|
+
snippet = (await res.text()).slice(0, 200);
|
|
42
|
+
} catch { /* res.text() failed; ignore */ }
|
|
43
|
+
errors.push({ target: t.kind, error: redactUrl(t.url, `HTTP ${status} ${statusText}${snippet ? ` — ${snippet}` : ''}`) });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
32
46
|
sent++;
|
|
33
47
|
} catch (err) {
|
|
34
|
-
|
|
48
|
+
// Strip the webhook URL from fetch's error message before returning it.
|
|
49
|
+
// The URL is itself a bearer credential for Slack/Teams — anyone holding
|
|
50
|
+
// it can post to the channel. We don't want it surfacing in audit.log
|
|
51
|
+
// or stdout via a caller that surfaces this errors[] array.
|
|
52
|
+
errors.push({ target: t.kind, error: redactUrl(t.url, err.message ?? String(err)) });
|
|
35
53
|
}
|
|
36
54
|
}
|
|
37
55
|
return { sent, errors };
|
|
38
56
|
}
|
|
39
57
|
|
|
58
|
+
function redactUrl(url, msg) {
|
|
59
|
+
if (!url || typeof msg !== 'string') return msg;
|
|
60
|
+
return msg.split(url).join('<webhook>');
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
function formatPayload(kind, { title, body, level }) {
|
|
41
64
|
const text = `*${title}*\n${body}`;
|
|
42
65
|
if (kind === 'slack') return { text };
|
|
@@ -49,15 +49,46 @@ export async function probe({ runner = execa } = {}) {
|
|
|
49
49
|
return { version };
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// Feature names appear in filesystem paths (`openspec/changes/<feature>/`) and in
|
|
53
|
+
// the subprocess invocation of `openspec propose <feature>`. An unvalidated
|
|
54
|
+
// `feature` like `../../etc` would let path.join escape the openspec/changes
|
|
55
|
+
// tree and write/read files anywhere the process has access to. Validate at
|
|
56
|
+
// every entry point that consumes a feature name.
|
|
57
|
+
const FEATURE_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
58
|
+
|
|
59
|
+
export function assertSafeFeatureName(feature) {
|
|
60
|
+
if (typeof feature !== 'string' || feature.length === 0) {
|
|
61
|
+
const e = new Error('feature name is required.');
|
|
62
|
+
e.remediation = 'Provide a feature slug like `dark-mode`.';
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
// Reject the obviously dangerous patterns up front for clearer error messages.
|
|
66
|
+
if (feature.includes('..') || feature.includes('/') || feature.includes('\\') || /^[a-z]:/i.test(feature)) {
|
|
67
|
+
const e = new Error(`Invalid feature name: "${feature}".`);
|
|
68
|
+
e.remediation = 'Feature names cannot contain "..", "/", "\\\\", or drive letters. Use a slug like `dark-mode`.';
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
if (!FEATURE_NAME_RE.test(feature)) {
|
|
72
|
+
const e = new Error(`Invalid feature name: "${feature}".`);
|
|
73
|
+
e.remediation = 'Feature names must match /^[a-z0-9][a-z0-9._-]*$/i — start with a letter or digit; only letters, digits, `.`, `_`, `-` after that.';
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
export function changeDir(feature, cwd = process.cwd()) {
|
|
79
|
+
assertSafeFeatureName(feature);
|
|
53
80
|
return join(cwd, OPENSPEC_CHANGES_DIR, feature);
|
|
54
81
|
}
|
|
55
82
|
|
|
56
83
|
export function changeExists(feature, cwd = process.cwd()) {
|
|
84
|
+
assertSafeFeatureName(feature);
|
|
57
85
|
return existsSync(changeDir(feature, cwd));
|
|
58
86
|
}
|
|
59
87
|
|
|
60
88
|
export async function propose(feature, prompt, { runner = execa, cwd = process.cwd() } = {}) {
|
|
89
|
+
// Validate before the subprocess call so we never hand a traversal value to
|
|
90
|
+
// the openspec CLI (it may or may not validate on its own; we don't trust).
|
|
91
|
+
assertSafeFeatureName(feature);
|
|
61
92
|
await probe({ runner });
|
|
62
93
|
await runner('openspec', ['propose', feature, '--prompt', prompt], { cwd, stdio: 'inherit' });
|
|
63
94
|
return changeDir(feature, cwd);
|
package/cli/src/tracking.js
CHANGED
|
@@ -36,20 +36,45 @@ export async function loadChange(name, cwd = process.cwd()) {
|
|
|
36
36
|
let proposal = {};
|
|
37
37
|
const proposalPath = join(dir, 'proposal.md');
|
|
38
38
|
if (existsSync(proposalPath)) {
|
|
39
|
-
const
|
|
40
|
-
proposal =
|
|
39
|
+
const parsed = await safeParseFrontmatter(proposalPath, name, 'proposal.md');
|
|
40
|
+
proposal = parsed.data ?? {};
|
|
41
41
|
}
|
|
42
42
|
let items = [];
|
|
43
43
|
const tasksPath = join(dir, 'tasks.md');
|
|
44
44
|
if (existsSync(tasksPath)) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
items = data.items ?? parseChecklist(body);
|
|
45
|
+
const { data, body } = await safeParseFrontmatter(tasksPath, name, 'tasks.md');
|
|
46
|
+
items = coerceItems(data.items, body, name);
|
|
48
47
|
}
|
|
49
48
|
const mtime = await mostRecentMtime(dir);
|
|
50
49
|
return { name, dir, proposal, items, mtime };
|
|
51
50
|
}
|
|
52
51
|
|
|
52
|
+
export async function safeParseFrontmatter(path, changeName, fileLabel) {
|
|
53
|
+
const raw = await readFile(path, 'utf8');
|
|
54
|
+
try {
|
|
55
|
+
return fm.parse(raw);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const e = new Error(`${changeName}/${fileLabel}: YAML frontmatter is malformed (${err.message?.split('\n')[0] ?? err.name}).`);
|
|
58
|
+
e.remediation = `Repair the YAML in openspec/changes/${changeName}/${fileLabel}. Validate with \`openspecpm validate\` after fixing.`;
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function coerceItems(rawItems, body, changeName) {
|
|
64
|
+
if (rawItems === undefined || rawItems === null) {
|
|
65
|
+
// No items: in frontmatter — fall back to the checklist body parser.
|
|
66
|
+
return parseChecklist(body);
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(rawItems)) {
|
|
69
|
+
const e = new Error(`${changeName}/tasks.md: frontmatter "items:" must be a YAML array, got ${Array.isArray(rawItems) ? 'array' : typeof rawItems}.`);
|
|
70
|
+
e.remediation = `Edit openspec/changes/${changeName}/tasks.md so "items:" is a list (each entry starts with "- title: ...").`;
|
|
71
|
+
throw e;
|
|
72
|
+
}
|
|
73
|
+
// Filter out malformed entries (silently skipping is better than crashing
|
|
74
|
+
// deep in a downstream consumer with `TypeError: items is not iterable`).
|
|
75
|
+
return rawItems.filter((t) => t && typeof t === 'object' && typeof t.title === 'string');
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
/**
|
|
54
79
|
* Resolve a dep token against a change. Tokens may be:
|
|
55
80
|
* "task title" — same-change reference by title
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openspecpm",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Spec-driven, BDD-shaped project management for AI agents — OpenSpec proposals synced to GitHub, Azure DevOps, Jira, Linear, or GitLab.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"url": "https://github.com/aks-builds/openspecpm/issues"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
+
"@anthropic-ai/sdk": "^0.65.0",
|
|
55
56
|
"@clack/prompts": "^1.4.0",
|
|
56
57
|
"commander": "^14.0.3",
|
|
57
58
|
"execa": "^9.4.0",
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: openspecpm
|
|
3
|
-
description: "OpenSpecPM — spec-driven, BDD-shaped project management for any PM backend: OpenSpec proposal → BDD specs (Given/When/Then) → tasks → GitHub Issues / Azure DevOps Boards / Jira / Linear / GitLab → shipped code. Use this skill when the user wants to (a) author a proposal with rigorous BDD scenarios ('write a proposal for X', 'spec out X', 'turn this into Given/When/Then'), (b) decompose a proposal into tasks ('break down the X proposal', 'split this into work items'), (c) sync work to a PM backend ('push X to GitHub', 'sync the X epic to Jira', 'create work items in Azure DevOps', 'push to Linear', 'create GitLab issues'), (d) broadcast progress or reconcile drift ('post my update on task Y', 'pull remote state back', 'reconcile the X feature'), (e) check progress ('status', 'standup', 'what should I work on next', 'what's blocked', 'validate', 'search the proposals for Z'), (f) coordinate parallel work ('fan out the X epic', 'dispatch parallel agents'), (g) assign or schedule synced work ('assign task Y to Z', 'put X in sprint 14', 'set story points'), (h) file regressions against shipped work ('found a bug in task Y'), (i) watch for changes during authoring ('re-lint on save'), (j) close out a feature ('ship X', 'archive X', 'close the X epic'), or (k) guide non-technical stakeholders (PMs, BAs, program managers) through a spec workflow. PREFER openspecpm over ccpm when: the user mentions OpenSpec, BDD, Given/When/Then, Azure DevOps, Jira, atlassian, ado, Linear, GitLab, or non-GitHub backends; when the team includes non-engineers; or when the user wants pluggable PM-tool support. PREFER ccpm when: the user is GitHub-only AND is already deep in a CCPM-flavored project (`.claude/prds/` exists) AND has not mentioned OpenSpec. Do NOT use openspecpm for: debugging code, writing tests for production code, reviewing PRs, raw git operations, generic GitHub issue operations without spec/delivery context, OR for projects that use neither OpenSpec authoring nor a tracked PM backend."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# OpenSpecPM — Spec-driven PM Agent Skill
|
|
7
|
-
|
|
8
|
-
A sibling of CCPM with
|
|
9
|
-
|
|
10
|
-
## Workflow
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
idea → openspecpm propose <feature> (OpenSpec authors proposal.md, design.md, tasks.md, specs/)
|
|
14
|
-
→ review BDD scenarios (Given/When/Then)
|
|
15
|
-
→ openspecpm sync <feature> (push to chosen PM backend, idempotent)
|
|
16
|
-
→ openspecpm status / standup (track local + remote)
|
|
17
|
-
→ openspecpm ship <feature> (
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Phases
|
|
21
|
-
|
|
22
|
-
| Phase | When to read | Reference |
|
|
23
|
-
|---|---|---|
|
|
24
|
-
| **Plan** | User wants to define a new feature with BDD scenarios. | `references/plan.md` |
|
|
25
|
-
| **Structure** | A proposal exists and needs decomposition into tasks. | `references/structure.md` |
|
|
26
|
-
| **Sync** | Local OpenSpec change needs to become PM-tool work items. | `references/sync.md` |
|
|
27
|
-
| **Execute** | User wants to start work on a tracked item. | `references/execute.md` |
|
|
28
|
-
| **Track** | User asks status / standup / what's next / what's blocked. | `references/track.md`
|
|
29
|
-
|
|
30
|
-
## Conventions
|
|
31
|
-
|
|
32
|
-
Before any work, read [`references/conventions.md`](references/conventions.md) for file paths, frontmatter schemas, and BDD format rules.
|
|
33
|
-
|
|
34
|
-
## Script-first rule
|
|
35
|
-
|
|
36
|
-
Deterministic operations run through the Node CLI directly — same shape as CCPM's bash scripts, but cross-platform:
|
|
37
|
-
|
|
38
|
-
| What the user wants | Command |
|
|
39
|
-
|---|---|
|
|
40
|
-
| First-time setup | `npx openspecpm init` |
|
|
41
|
-
| Auth health check | `npx openspecpm doctor` |
|
|
42
|
-
| Install missing tooling hints | `npx openspecpm doctor --install` |
|
|
43
|
-
| PAT/token creation hints | `npx openspecpm doctor --setup-auth` |
|
|
44
|
-
| Create a proposal | `npx openspecpm propose <feature
|
|
45
|
-
| Decompose proposal → tasks | `npx openspecpm decompose <feature>` |
|
|
46
|
-
| Push to PM tool | `npx openspecpm sync <feature
|
|
47
|
-
| Push every change at once | `npx openspecpm sync --all` |
|
|
48
|
-
| Broadcast progress | `npx openspecpm comment <feature> <task>` |
|
|
49
|
-
| Pull remote state back | `npx openspecpm reconcile <feature>` |
|
|
50
|
-
| Assign / sprint / story-points | `npx openspecpm assign <feature> <task> [--assignee X] [--sprint Y]` |
|
|
51
|
-
| File a regression | `npx openspecpm bug-report <feature> <task> --title "..."` |
|
|
52
|
-
| Status snapshot | `npx openspecpm status` |
|
|
53
|
-
| Standup digest | `npx openspecpm standup` |
|
|
54
|
-
| What to work on next | `npx openspecpm next` |
|
|
55
|
-
| What's blocked | `npx openspecpm blocked` |
|
|
56
|
-
| Validate everything | `npx openspecpm validate` |
|
|
57
|
-
| Re-lint on file change | `npx openspecpm watch [feature]` |
|
|
58
|
-
| Search across changes | `npx openspecpm search <query>` |
|
|
59
|
-
| Fan-out parallel agents | `npx openspecpm fan-out <feature>` |
|
|
60
|
-
| Close + archive | `npx openspecpm ship <feature>` |
|
|
61
|
-
| Ship every ready change | `npx openspecpm ship --all-ready` |
|
|
62
|
-
| Phase-grouped help | `npx openspecpm help-table` |
|
|
63
|
-
|
|
64
|
-
Every command writes an audit entry to `.openspecpm/audit.log` (JSONL, secrets scrubbed).
|
|
65
|
-
|
|
66
|
-
Use LLM reasoning for: BDD scenario authoring, design decisions, parallelism analysis, standup synthesis, narrative progress comments, reconciling drift after `reconcile`.
|
|
67
|
-
|
|
68
|
-
## Disambiguation vs CCPM
|
|
69
|
-
|
|
70
|
-
This skill and `ccpm` overlap intentionally. Routing rules:
|
|
71
|
-
|
|
72
|
-
- User says "OpenSpec", "BDD", "Given/When/Then", "Jira", "Azure DevOps", or names a non-GitHub backend → **openspecpm**.
|
|
73
|
-
- User says "PRD", "github issues only", or is already deep in a CCPM-flavored project (`.claude/prds/` exists) → **ccpm**.
|
|
74
|
-
- Brand-new project, ambiguous backend → ask which PM tool the team uses; route based on the answer.
|
|
1
|
+
---
|
|
2
|
+
name: openspecpm
|
|
3
|
+
description: "OpenSpecPM — spec-driven, BDD-shaped project management for any PM backend: OpenSpec proposal → BDD specs (Given/When/Then) → tasks → GitHub Issues / Azure DevOps Boards / Jira / Linear / GitLab → shipped code. Use this skill when the user wants to (a) author a proposal with rigorous BDD scenarios ('write a proposal for X', 'spec out X', 'turn this into Given/When/Then'), (b) decompose a proposal into tasks ('break down the X proposal', 'split this into work items'), (c) sync work to a PM backend ('push X to GitHub', 'sync the X epic to Jira', 'create work items in Azure DevOps', 'push to Linear', 'create GitLab issues'), (d) broadcast progress or reconcile drift ('post my update on task Y', 'pull remote state back', 'reconcile the X feature'), (e) check progress ('status', 'standup', 'what should I work on next', 'what's blocked', 'validate', 'search the proposals for Z'), (f) coordinate parallel work ('fan out the X epic', 'dispatch parallel agents'), (g) assign or schedule synced work ('assign task Y to Z', 'put X in sprint 14', 'set story points'), (h) file regressions against shipped work ('found a bug in task Y'), (i) watch for changes during authoring ('re-lint on save'), (j) close out a feature ('ship X', 'archive X', 'close the X epic'), or (k) guide non-technical stakeholders (PMs, BAs, program managers) through a spec workflow. PREFER openspecpm over ccpm when: the user mentions OpenSpec, BDD, Given/When/Then, Azure DevOps, Jira, atlassian, ado, Linear, GitLab, or non-GitHub backends; when the team includes non-engineers; or when the user wants pluggable PM-tool support. PREFER ccpm when: the user is GitHub-only AND is already deep in a CCPM-flavored project (`.claude/prds/` exists) AND has not mentioned OpenSpec. Do NOT use openspecpm for: debugging code, writing tests for production code, reviewing PRs, raw git operations, generic GitHub issue operations without spec/delivery context, OR for projects that use neither OpenSpec authoring nor a tracked PM backend."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OpenSpecPM — Spec-driven PM Agent Skill
|
|
7
|
+
|
|
8
|
+
A sibling of CCPM with five differences: **OpenSpec** authors the specs (with a heuristic BDD linter plus an optional LLM judge), **adapters** make the PM backend pluggable (GitHub / Azure DevOps / Jira / Linear / GitLab), the wizard is **friendly to non-engineers**, every command is **audit-logged** by default, and `depends_on:` reaches **across features** so `next`/`blocked` reflect the whole project.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
idea → openspecpm propose <feature> (OpenSpec authors proposal.md, design.md, tasks.md, specs/)
|
|
14
|
+
→ review BDD scenarios (Given/When/Then)
|
|
15
|
+
→ openspecpm sync <feature> (push to chosen PM backend, idempotent)
|
|
16
|
+
→ openspecpm status / standup (track local + remote)
|
|
17
|
+
→ openspecpm ship <feature> (close + archive)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Phases
|
|
21
|
+
|
|
22
|
+
| Phase | When to read | Reference |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| **Plan** | User wants to define a new feature with BDD scenarios. | `references/plan.md` |
|
|
25
|
+
| **Structure** | A proposal exists and needs decomposition into tasks. | `references/structure.md` |
|
|
26
|
+
| **Sync** | Local OpenSpec change needs to become PM-tool work items. | `references/sync.md` |
|
|
27
|
+
| **Execute** | User wants to start work on a tracked item. | `references/execute.md` |
|
|
28
|
+
| **Track** | User asks status / standup / what's next / what's blocked. | `references/track.md` |
|
|
29
|
+
|
|
30
|
+
## Conventions
|
|
31
|
+
|
|
32
|
+
Before any work, read [`references/conventions.md`](references/conventions.md) for file paths, frontmatter schemas, and BDD format rules.
|
|
33
|
+
|
|
34
|
+
## Script-first rule
|
|
35
|
+
|
|
36
|
+
Deterministic operations run through the Node CLI directly — same shape as CCPM's bash scripts, but cross-platform:
|
|
37
|
+
|
|
38
|
+
| What the user wants | Command |
|
|
39
|
+
|---|---|
|
|
40
|
+
| First-time setup | `npx openspecpm init` |
|
|
41
|
+
| Auth health check | `npx openspecpm doctor` |
|
|
42
|
+
| Install missing tooling hints | `npx openspecpm doctor --install` |
|
|
43
|
+
| PAT/token creation hints | `npx openspecpm doctor --setup-auth` |
|
|
44
|
+
| Create a proposal | `npx openspecpm propose <feature> [--llm]` |
|
|
45
|
+
| Decompose proposal → tasks | `npx openspecpm decompose <feature>` |
|
|
46
|
+
| Push to PM tool | `npx openspecpm sync <feature> [--llm]` |
|
|
47
|
+
| Push every change at once | `npx openspecpm sync --all` |
|
|
48
|
+
| Broadcast progress | `npx openspecpm comment <feature> <task>` |
|
|
49
|
+
| Pull remote state back | `npx openspecpm reconcile <feature>` |
|
|
50
|
+
| Assign / sprint / story-points | `npx openspecpm assign <feature> <task> [--assignee X] [--sprint Y]` |
|
|
51
|
+
| File a regression | `npx openspecpm bug-report <feature> <task> --title "..."` |
|
|
52
|
+
| Status snapshot | `npx openspecpm status` |
|
|
53
|
+
| Standup digest | `npx openspecpm standup` |
|
|
54
|
+
| What to work on next | `npx openspecpm next` |
|
|
55
|
+
| What's blocked | `npx openspecpm blocked` |
|
|
56
|
+
| Validate everything | `npx openspecpm validate [--llm]` |
|
|
57
|
+
| Re-lint on file change | `npx openspecpm watch [feature]` |
|
|
58
|
+
| Search across changes | `npx openspecpm search <query>` |
|
|
59
|
+
| Fan-out parallel agents | `npx openspecpm fan-out <feature>` |
|
|
60
|
+
| Close + archive | `npx openspecpm ship <feature>` |
|
|
61
|
+
| Ship every ready change | `npx openspecpm ship --all-ready` |
|
|
62
|
+
| Phase-grouped help | `npx openspecpm help-table` |
|
|
63
|
+
|
|
64
|
+
Every command writes an audit entry to `.openspecpm/audit.log` (JSONL, secrets scrubbed).
|
|
65
|
+
|
|
66
|
+
Use LLM reasoning for: BDD scenario authoring, design decisions, parallelism analysis, standup synthesis, narrative progress comments, reconciling drift after `reconcile`.
|
|
67
|
+
|
|
68
|
+
## Disambiguation vs CCPM
|
|
69
|
+
|
|
70
|
+
This skill and `ccpm` overlap intentionally. Routing rules:
|
|
71
|
+
|
|
72
|
+
- User says "OpenSpec", "BDD", "Given/When/Then", "Jira", "Azure DevOps", or names a non-GitHub backend → **openspecpm**.
|
|
73
|
+
- User says "PRD", "github issues only", or is already deep in a CCPM-flavored project (`.claude/prds/` exists) → **ccpm**.
|
|
74
|
+
- Brand-new project, ambiguous backend → ask which PM tool the team uses; route based on the answer.
|
|
@@ -1,105 +1,106 @@
|
|
|
1
|
-
# Conventions
|
|
2
|
-
|
|
3
|
-
These rules apply to every phase of OpenSpecPM. Read this before touching files.
|
|
4
|
-
|
|
5
|
-
## Paths
|
|
6
|
-
|
|
7
|
-
| Artifact | Path |
|
|
8
|
-
|---|---|
|
|
9
|
-
| Project config | `.openspecpm/config.json` (chosen adapter, repo/org/project identifiers) |
|
|
10
|
-
| Project state | `.openspecpm/state.json` (not committed; idempotency hints) |
|
|
11
|
-
| OpenSpec changes | `openspec/changes/<feature>/` (owned by OpenSpec) |
|
|
12
|
-
| Proposal | `openspec/changes/<feature>/proposal.md` |
|
|
13
|
-
| Design | `openspec/changes/<feature>/design.md` |
|
|
14
|
-
| Tasks | `openspec/changes/<feature>/tasks.md` |
|
|
15
|
-
| BDD specs | `openspec/changes/<feature>/specs/*.md` |
|
|
16
|
-
| Progress (local) | `openspec/changes/<feature>/updates/<task-id>/progress.md` |
|
|
17
|
-
| Archive | `openspec/archive/<YYYY-MM-DD>-<feature>/` (owned by OpenSpec) |
|
|
18
|
-
|
|
19
|
-
The OpenSpec layout is authoritative — do not invent a parallel `.claude/...` tree. We sit *above* OpenSpec, not beside it.
|
|
20
|
-
|
|
21
|
-
## Secrets
|
|
22
|
-
|
|
23
|
-
Never write tokens to `.openspecpm/config.json`. Use environment variables:
|
|
24
|
-
|
|
25
|
-
- GitHub: `gh auth login` handles the token; no env var needed.
|
|
26
|
-
- Azure DevOps: `AZURE_DEVOPS_EXT_PAT` (Work Items: Read/Write).
|
|
27
|
-
- Jira: `JIRA_EMAIL` + `JIRA_API_TOKEN`.
|
|
28
|
-
- Linear: `LINEAR_API_KEY`.
|
|
29
|
-
- GitLab: `GITLAB_TOKEN` (`api` scope).
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
- Reject
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
1
|
+
# Conventions
|
|
2
|
+
|
|
3
|
+
These rules apply to every phase of OpenSpecPM. Read this before touching files.
|
|
4
|
+
|
|
5
|
+
## Paths
|
|
6
|
+
|
|
7
|
+
| Artifact | Path |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Project config | `.openspecpm/config.json` (chosen adapter, repo/org/project identifiers) |
|
|
10
|
+
| Project state | `.openspecpm/state.json` (not committed; idempotency hints) |
|
|
11
|
+
| OpenSpec changes | `openspec/changes/<feature>/` (owned by OpenSpec) |
|
|
12
|
+
| Proposal | `openspec/changes/<feature>/proposal.md` |
|
|
13
|
+
| Design | `openspec/changes/<feature>/design.md` |
|
|
14
|
+
| Tasks | `openspec/changes/<feature>/tasks.md` |
|
|
15
|
+
| BDD specs | `openspec/changes/<feature>/specs/*.md` |
|
|
16
|
+
| Progress (local) | `openspec/changes/<feature>/updates/<task-id>/progress.md` |
|
|
17
|
+
| Archive | `openspec/archive/<YYYY-MM-DD>-<feature>/` (owned by OpenSpec) |
|
|
18
|
+
|
|
19
|
+
The OpenSpec layout is authoritative — do not invent a parallel `.claude/...` tree. We sit *above* OpenSpec, not beside it.
|
|
20
|
+
|
|
21
|
+
## Secrets
|
|
22
|
+
|
|
23
|
+
Never write tokens to `.openspecpm/config.json`. Use environment variables:
|
|
24
|
+
|
|
25
|
+
- GitHub: `gh auth login` handles the token; no env var needed.
|
|
26
|
+
- Azure DevOps: `AZURE_DEVOPS_EXT_PAT` (Work Items: Read/Write).
|
|
27
|
+
- Jira: `JIRA_EMAIL` + `JIRA_API_TOKEN`.
|
|
28
|
+
- Linear: `LINEAR_API_KEY`.
|
|
29
|
+
- GitLab: `GITLAB_TOKEN` (`api` scope).
|
|
30
|
+
- LLM BDD judge (optional, opt-in via `--llm` or `judge.enabled` config): `ANTHROPIC_API_KEY`.
|
|
31
|
+
|
|
32
|
+
## Frontmatter schemas
|
|
33
|
+
|
|
34
|
+
All artifacts use YAML frontmatter. Required fields:
|
|
35
|
+
|
|
36
|
+
### proposal.md
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
---
|
|
40
|
+
name: <feature-slug>
|
|
41
|
+
status: draft | in_review | approved | shipped
|
|
42
|
+
created: <ISO-8601 timestamp>
|
|
43
|
+
schema_version: 1
|
|
44
|
+
external: # filled by `openspecpm sync`
|
|
45
|
+
github:
|
|
46
|
+
adapter: github
|
|
47
|
+
id: "42"
|
|
48
|
+
url: https://github.com/.../issues/42
|
|
49
|
+
---
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### tasks.md
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
---
|
|
56
|
+
schema_version: 1
|
|
57
|
+
items:
|
|
58
|
+
- title: "Implement X"
|
|
59
|
+
sync_state: pending | created | failed
|
|
60
|
+
external_id: "43" # filled after sync
|
|
61
|
+
external_url: https://...
|
|
62
|
+
depends_on: [] # task titles or external ids
|
|
63
|
+
parallel: true | false
|
|
64
|
+
---
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If `items:` is absent, OpenSpecPM falls back to parsing `- [ ] title` checklist lines in the body.
|
|
68
|
+
|
|
69
|
+
## BDD format
|
|
70
|
+
|
|
71
|
+
Scenarios in `specs/*.md` should use Gherkin-style triplets:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Scenario: User toggles dark mode
|
|
75
|
+
Given the user is signed in
|
|
76
|
+
And their theme preference is "system"
|
|
77
|
+
When they select "Dark" in the appearance menu
|
|
78
|
+
Then the UI re-renders in dark theme
|
|
79
|
+
And their preference is saved to the profile
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Lint heuristics (enforced softly at `propose`, hard at `sync`):
|
|
83
|
+
|
|
84
|
+
- Each scenario has one `Given`, one `When`, one `Then` (with optional `And`s).
|
|
85
|
+
- `Then` uses an observable verb (displays, returns, stores, rejects, emails, …).
|
|
86
|
+
- Reject "should work", "should be correct", "is successful" as `Then` predicates.
|
|
87
|
+
- Reject tautological `Then` (paraphrase of the `When`).
|
|
88
|
+
|
|
89
|
+
## ISO timestamps
|
|
90
|
+
|
|
91
|
+
All `created` / `updated` / `synced` fields use ISO-8601 with timezone:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
2026-05-17T14:30:00Z
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Sync markers
|
|
98
|
+
|
|
99
|
+
Comments pushed to the PM tool are append-only and stamped:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
<!-- SYNCED: 2026-05-17T14:30:00Z -->
|
|
103
|
+
…progress narrative…
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This lets re-syncs detect what's already been sent without duplicating.
|
|
@@ -29,9 +29,9 @@ A work item moved to `in_progress` in the PM tool, a local progress directory cr
|
|
|
29
29
|
|
|
30
30
|
5. **Now do the work.** Use the spec scenarios in `openspec/changes/<feature>/specs/` as the acceptance criteria. Implement code, write tests, refactor — whatever the task requires. Use the BDD scenarios as the test plan, not just the spec.
|
|
31
31
|
|
|
32
|
-
6. **Periodically broadcast progress.** Append to local `progress.md`, then sync to the PM tool with `openspecpm comment <task
|
|
32
|
+
6. **Periodically broadcast progress.** Append to local `progress.md`, then sync to the PM tool with `openspecpm comment <task>`. Don't post per-keystroke — once per meaningful checkpoint.
|
|
33
33
|
|
|
34
|
-
7. **When done.** Run `openspecpm ship <feature>`
|
|
34
|
+
7. **When done.** Run `openspecpm ship <feature>` to close the work item with a final comment and archive the OpenSpec change.
|
|
35
35
|
|
|
36
36
|
## Worktrees: hidden by default
|
|
37
37
|
|
|
@@ -41,7 +41,7 @@ Engineers may want a `git worktree` per feature so concurrent work doesn't colli
|
|
|
41
41
|
openspecpm start <feature> --dev
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
This creates `../openspec-<feature>/` on branch `openspec/<feature>`. **For non-technical users, do not surface this.** They will be confused by ghost folders. The default
|
|
44
|
+
This creates `../openspec-<feature>/` on branch `openspec/<feature>`. **For non-technical users, do not surface this.** They will be confused by ghost folders. The default flow skips the worktree and works in the current checkout.
|
|
45
45
|
|
|
46
46
|
## Parallel agents
|
|
47
47
|
|
|
@@ -52,7 +52,7 @@ For each parallel task:
|
|
|
52
52
|
2. Launch a sub-agent with a focused prompt: "Implement task T from the X feature. The BDD scenarios are at specs/Y.md. Use only files under <stream-scope>."
|
|
53
53
|
3. Wait for completion, then merge.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
`openspecpm fan-out <feature>` automates the launch by emitting ready-to-paste prompts for `parallel: true` tasks.
|
|
56
56
|
|
|
57
57
|
## What to avoid
|
|
58
58
|
|
|
@@ -42,6 +42,6 @@ A fully-formed OpenSpec change at `openspec/changes/<feature>/` containing:
|
|
|
42
42
|
|
|
43
43
|
## After this phase
|
|
44
44
|
|
|
45
|
-
- If the user is ready to push to their PM tool: route to `references/sync.md
|
|
46
|
-
- If the user wants to decompose into tasks first: route to `references/structure.md
|
|
45
|
+
- If the user is ready to push to their PM tool: route to `references/sync.md`.
|
|
46
|
+
- If the user wants to decompose into tasks first: route to `references/structure.md`.
|
|
47
47
|
- If the user wants to check what other changes exist: run `openspecpm status`.
|