projectstore-claude 0.30.10 → 0.30.11

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.
@@ -12,7 +12,7 @@
12
12
  "name": "projectstore",
13
13
  "displayName": "projectstore",
14
14
  "description": "📚 Your agent runs the project through a verified loop: task → artifact (ADR · spec · epic · story) → adversarial critic → backlog → planner → reviewer → done. Plain markdown in an Obsidian-friendly vault, every write approved by you — and any model can pick the project up tomorrow.",
15
- "version": "0.30.10",
15
+ "version": "0.30.11",
16
16
  "author": {
17
17
  "name": "Evgenii Konev",
18
18
  "email": "ekonev@smartandpoint.com",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "projectstore",
3
3
  "displayName": "projectstore",
4
- "version": "0.30.10",
4
+ "version": "0.30.11",
5
5
  "description": "Your agent runs the project through a verified loop: task → artifact (ADR / spec / epic / story) → adversarial critic → backlog → planner → reviewer → done. Plain markdown in git — any model can pick the project up tomorrow.",
6
6
  "author": {
7
7
  "name": "Evgenii Konev @ SmartAndPoint",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Create a new story inside an existing epic, or run its lifecycle gates (plan / close).
3
- argument-hint: <epic-id> <title> [--spec SPEC-ID] | plan <story> | close <story>
3
+ argument-hint: <epic-id> <title> [--spec SPEC-ID] [--variant test] | plan <story> | close <story>
4
4
  ---
5
5
 
6
6
  You are managing a story: creating one, or running its lifecycle gates.
@@ -18,12 +18,21 @@ the two cannot collide).
18
18
 
19
19
  2. **Validate args**: epic-id (positional 1) + title (rest). If only one word, ask for the title. An optional `--spec SPEC-ID` names the covering spec — put it into the rendered draft's `specs:` list (inline flow: `specs: ["SPEC-001"]`). Under `spec_policy: required` (vault's `.projectstore.json`), remind that a story needs a covering spec before implementation starts, or a `no_spec` reason declared at the plan gate.
20
20
 
21
+ An optional `--variant test` makes a **test story**: one that plans the
22
+ tests for a spec's contracts, written to the `story.test` guide. Only
23
+ `test` is defined: refuse another value and name `test`. Remove `--variant test` and `--spec SPEC-ID` with its
24
+ value from the arguments before any `draft.mjs` call, in step 3 and in the
25
+ re-run of step 6: the script reads every remaining word as the title, so a
26
+ flag left in would end up in the title and the slug.
27
+
21
28
  2a. **Read the guide** for this kind before writing anything:
22
29
 
23
30
  ```bash
24
31
  node "${CLAUDE_PLUGIN_ROOT}/bin/projectstore.mjs" guide story --project "${CLAUDE_PROJECT_DIR}"
25
32
  ```
26
33
 
34
+ For a test story read `guide story.test` instead, in the same form.
35
+
27
36
  Write the draft to its `## Must`, `## Common mistakes`, `## Do not write`
28
37
  and `## Length`: the critic checks the same `## Must` items. Exit 1 means
29
38
  this kind has no guide; say so and go on.
@@ -31,10 +40,15 @@ the two cannot collide).
31
40
  3. **Render draft**:
32
41
 
33
42
  ```bash
34
- node "${CLAUDE_PLUGIN_ROOT}/scripts/draft.mjs" story "$ARGUMENTS"
43
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/draft.mjs" story <epic-id> "<title>"
35
44
  ```
36
45
 
37
- The script fails if the epic folder does not exist. Surface the error and suggest `/projectstore:epic <id> "<title>"` first.
46
+ The epic id and the title only, with the flags of step 2 removed. The
47
+ script fails if the epic folder does not exist. Surface the error and suggest `/projectstore:epic <id> "<title>"` first.
48
+
49
+ 3a. **For a test story**, replace the draft's commented `# variant: test …`
50
+ line with `variant: test` before the preview, the way `--spec` fills
51
+ `specs:`. The critic then reviews the story against `story.test`.
38
52
 
39
53
  4. **Preview**: path + first ~25 lines.
40
54
 
@@ -42,9 +56,10 @@ the two cannot collide).
42
56
 
43
57
  When prompting "Edit", note that the story template has a `Decomposition` checklist — if the user wants to seed it with concrete tasks from the current conversation, regenerate with those tasks pre-filled in place of the empty checkboxes.
44
58
 
45
- 6. **Post-approval race re-check** (Layer 1): re-run `draft.mjs story "$ARGUMENTS"` and re-read its `collision` field — an exact-name `test -e` cannot see normalized cross-era collisions (`story-006-foo.md` vs `story-foo.md`). If `collision` is non-null, surface it as a topic collision (`"<identity>" already exists as <with>`), and ask: extend the existing story, pick a different slug (`-2` is a deliberate distinct identity), or cancel. Render `warnings` entries as `⚠️` lines in the preview too.
59
+ 6. **Post-approval race re-check** (Layer 1): re-run `draft.mjs story <epic-id> "<title>"` (the same stripped arguments as step 3) and re-read its `collision` field — an exact-name `test -e` cannot see normalized cross-era collisions (`story-006-foo.md` vs `story-foo.md`). If `collision` is non-null, surface it as a topic collision (`"<identity>" already exists as <with>`), and ask: extend the existing story, pick a different slug (`-2` is a deliberate distinct identity), or cancel. Render `warnings` entries as `⚠️` lines in the preview too.
46
60
 
47
- 7. **On Yes** (path free): Write file.
61
+ 7. **On Yes** (path free): Write the file you previewed, with the step-2 and
62
+ step-3a edits (`specs:`, `variant: test`), not the re-run's raw draft.
48
63
 
49
64
  8. **Suggest next**: "Now decompose the work in the `Decomposition` section, or run `/projectstore:kanban` to refresh the board. Before implementation: `/projectstore:story plan <story>`."
50
65
 
@@ -4,7 +4,10 @@ A spec's behavioral contracts are numbered and testable. This page shows how
4
4
  to write a contract so a test follows from it, how a test cites the contract
5
5
  it covers, and what doctor reports. It describes Part A of the ADR *Spec
6
6
  policy, revised: contract scenarios and test citations that doctor counts,
7
- and a declared per-story opt-out from a spec*.
7
+ and a declared per-story opt-out from a spec*, and decisions 6 and 7 of the
8
+ ADR *One authoring guide per artifact kind, read by the author before the
9
+ draft and by the critic after, overridable from the vault* (edge and failure
10
+ marks, and test stories).
8
11
 
9
12
  All of it is optional. A spec without scenarios, and a test without a
10
13
  citation, stay valid.
@@ -31,8 +34,75 @@ The items are a list, so Markdown keeps one step per line. The keywords stay
31
34
  in English in every language, like `— stories:`: a tool matches them
32
35
  literally.
33
36
 
34
- Doctor reads scenarios for one thing only. If a contract's scenario has no
35
- `WHEN` (or `GIVEN`), or no `THEN`, it reports an info.
37
+ Doctor reads scenarios for two things. If a contract's scenario has no
38
+ `WHEN` (or `GIVEN`), or no `THEN`, it reports an info. And it counts the
39
+ scenarios marked as an edge or a failure case, below.
40
+
41
+ An item may wrap: a line indented deeper than its dash, before a blank line,
42
+ belongs to the item above it.
43
+
44
+ ## Marking an edge or a failure case
45
+
46
+ A scenario's `WHEN` or `GIVEN` item may end with `(edge)` or `(failure)`:
47
+
48
+ ```markdown
49
+ 3. **A stale index is reported.** Doctor compares each folder index with the
50
+ artifacts on disk and reports a row that no longer matches.
51
+ - GIVEN an index row whose status differs from the artifact's frontmatter
52
+ - WHEN doctor runs with --vault
53
+ - THEN it reports an index finding that names the folder
54
+ - GIVEN a folder whose README is missing (edge)
55
+ - WHEN doctor runs with --vault
56
+ - THEN it reports the missing index, and nothing else in that folder
57
+ ```
58
+
59
+ `(edge)` is for a case that comes from the input or the state: boundary
60
+ values, invalid input, a state transition, an upgrade from an older state.
61
+ `(failure)` is for a case where something else fails: a dependency, a retry
62
+ or a concurrent run, a partial failure, a rollback. A happy-path scenario
63
+ has no mark.
64
+
65
+ The mark is lowercase English in ASCII parentheses, at the very end of the
66
+ item (on its last line when the item wraps). A mark on an `AND` or `THEN`
67
+ item, in the contract's prose, inside backticks, or followed by more text
68
+ does not count.
69
+
70
+ For each active spec, doctor lists the contracts that carry scenarios and
71
+ have none marked: their scenarios show only the happy path. It counts the
72
+ marks and does not judge them. Whether a marked scenario is a real edge case
73
+ is the critic's question.
74
+
75
+ ## Writing a test story
76
+
77
+ A test story plans the tests for one or more contracts. Create it with the
78
+ variant flag, and its draft is written to the test lead's guide, `story.test`:
79
+
80
+ ```
81
+ /projectstore:story PS-X "Tests for the stale-index contract" --variant test
82
+ ```
83
+
84
+ The guide asks the story to name the contracts it covers by token, to list
85
+ the cases that apply to each (boundary values, invalid input, state
86
+ transitions, failure of a dependency, retry and concurrency, partial failure,
87
+ upgrade from an older state, rollback), to say which cases it leaves untested
88
+ and why, and to require a citation on every test it adds. For example:
89
+
90
+ ```markdown
91
+ ## Description
92
+
93
+ Covers [example#3]. The scenario shows the happy path; the cases below are
94
+ the ones a production vault hits.
95
+
96
+ ## Decomposition
97
+
98
+ - [ ] Boundary: an index with no rows, and one with a row per artifact.
99
+ - [ ] Invalid input: a row whose link points at no file.
100
+ - [ ] Partial failure: one folder's README unreadable, the others fine.
101
+ - [ ] Not tested: concurrency. Doctor is read-only, so two runs cannot race.
102
+ ```
103
+
104
+ The story has `variant: test` in its frontmatter and follows every other
105
+ story rule: the plan and close gates, evidence, the board.
36
106
 
37
107
  ## A contract no test can cover
38
108
 
@@ -97,6 +167,7 @@ skipped. Tests outside the bound project, or in a submodule, are not seen.
97
167
  | `citation-unknown` | warn | A token whose key is no spec's id or alias, or whose number is not a contract of that spec |
98
168
  | `citation-superseded` | info | A token that cites a superseded spec |
99
169
  | `scenario-shape` | info | A scenario without a WHEN/GIVEN or a THEN |
170
+ | `contract-happy-path-only` | info | One line per active spec: the contracts that carry scenarios and have none marked `(edge)` or `(failure)` |
100
171
  | `spec-alias` | issue | A duplicate alias, an alias equal to another spec's id, or the reserved `example` |
101
172
  | `evidence-citation` | warn | Under `lifecycle_gates: on`: a story's evidence names a token that no file in the project carries |
102
173
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectstore",
3
- "version": "0.30.10",
3
+ "version": "0.30.11",
4
4
  "description": "Your agent runs the project through a verified loop: task → artifact (ADR / spec / epic / story) → adversarial critic → backlog → planner → reviewer → done. Plain markdown in git — any model can pick the project up tomorrow.",
5
5
  "keywords": [
6
6
  "project-management",
@@ -0,0 +1,34 @@
1
+ ---
2
+ guide_for: story.test
3
+ guide_version: 1
4
+ default_review: false
5
+ ---
6
+
7
+ ## Lens
8
+
9
+ The test lead who reads a contract and asks what will break it in production. The scenarios show the happy path; this story covers the cases around it.
10
+
11
+ ## Must
12
+
13
+ - Names the spec contracts it covers by citation token.
14
+ - Lists, for each contract, the cases that apply out of: boundary values, invalid input, state transitions, failure of a dependency, retry and concurrency, partial failure, upgrade from an older state, rollback.
15
+ - States which cases it deliberately leaves untested, and why.
16
+ - Requires every test it adds to cite the contract it covers.
17
+
18
+ ## Common mistakes
19
+
20
+ - Restating the contract's scenarios as the test list. Add the cases they miss, beyond any already marked `(edge)` or `(failure)`.
21
+ - All eight cases under every contract. Keep the ones this contract can meet, and name the ones you dropped.
22
+ - A case with no observable outcome ("handles errors"). Name what is asserted: an exit code, a finding, a file left byte-identical.
23
+ - A citation token for a real key in prose or an example. Use the reserved key, as in `[example#3]`.
24
+ - Acceptance criteria that repeat the decomposition ("write the tests") instead of an outcome a reviewer can run.
25
+
26
+ ## Do not write
27
+
28
+ - Test code or assertions: the story names the cases, the tests hold the asserts.
29
+ - A covered contract restated in full: cite it by token.
30
+ - A case for behaviour no covered contract promises.
31
+
32
+ ## Length
33
+
34
+ A short description; one case list per covered contract, at most four contracts; four to ten criteria, each tied to a case; the plan a thin route of at most 40 lines.
@@ -2698,6 +2698,9 @@ export function parseSpecContracts(spec) {
2698
2698
  const contracts = [];
2699
2699
  let cur = null;
2700
2700
  let inFirstPara = false;
2701
+ // The scenario item a continuation line belongs to (SPEC-001 contract 13):
2702
+ // a line indented deeper than its dash, not a bullet, before a blank line.
2703
+ let open = null;
2701
2704
  for (const line of lines) {
2702
2705
  const top = line.match(/^(\d+[′″']*)\.(\s|$)/);
2703
2706
  if (top) {
@@ -2707,15 +2710,18 @@ export function parseSpecContracts(spec) {
2707
2710
  stepIndent: null,
2708
2711
  scenarios: [],
2709
2712
  marker: null,
2713
+ marks: [],
2710
2714
  first: line,
2711
2715
  };
2712
2716
  contracts.push(cur);
2713
2717
  inFirstPara = true;
2718
+ open = null;
2714
2719
  continue;
2715
2720
  }
2716
2721
  if (!cur) continue;
2717
2722
  if (/^\s*$/.test(line)) {
2718
2723
  inFirstPara = false;
2724
+ open = null;
2719
2725
  continue;
2720
2726
  }
2721
2727
  if (inFirstPara) cur.first += " " + line.trim();
@@ -2723,14 +2729,24 @@ export function parseSpecContracts(spec) {
2723
2729
  if (step) {
2724
2730
  if (cur.stepIndent === null) cur.stepIndent = step[1].length;
2725
2731
  if (step[1].length === cur.stepIndent) cur.steps.add(contractNumberAscii(step[2]));
2732
+ open = null;
2726
2733
  continue;
2727
2734
  }
2728
- const sc = line.match(/^\s*-\s+(WHEN|GIVEN|AND|THEN)\b/);
2729
- if (sc) cur.scenarios.push(line.trim());
2735
+ const sc = line.match(/^(\s*)-\s+(WHEN|GIVEN|AND|THEN)\b/);
2736
+ if (sc) {
2737
+ cur.scenarios.push(line.trim());
2738
+ open = { i: cur.scenarios.length - 1, indent: sc[1].length };
2739
+ continue;
2740
+ }
2741
+ if (open && line.match(/^\s*/)[0].length > open.indent && !/^\s*[-*+]\s/.test(line))
2742
+ cur.scenarios[open.i] += " " + line.trim();
2743
+ else open = null;
2730
2744
  }
2731
2745
  for (const c of contracts) {
2732
2746
  const mk = c.first.match(/\(verified\s*[::]\s*(live|manual)\)/);
2733
2747
  c.marker = mk ? mk[1] : null;
2748
+ // An (edge) or (failure) mark closes a WHEN or GIVEN item; nowhere else counts.
2749
+ c.marks = c.scenarios.map((l) => /^-\s+(WHEN|GIVEN)\b.*\((edge|failure)\)\s*$/.exec(l)?.[2]).filter(Boolean);
2734
2750
  delete c.first;
2735
2751
  delete c.stepIndent;
2736
2752
  }
@@ -2808,8 +2824,30 @@ export function checkSpecAliases(artifacts) {
2808
2824
  return out;
2809
2825
  }
2810
2826
 
2827
+ // Happy path only (SPEC-001 contract 13; the authoring-guides ADR, decision
2828
+ // 7): the contracts of an active spec that carry scenarios and have none
2829
+ // marked (edge) or (failure). Counted, not judged; no git.
2830
+ export function checkHappyPathOnly(artifacts) {
2831
+ const out = [];
2832
+ for (const s of artifacts.filter((a) => a.kind === "spec" && String(a.fm.status || "") === "active")) {
2833
+ const withScenarios = (parseSpecContracts(s) || []).filter((c) => c.scenarios.length);
2834
+ const bare = withScenarios.filter((c) => !c.marks.length).map((c) => c.n);
2835
+ if (!bare.length) continue;
2836
+ out.push(
2837
+ finding(
2838
+ "vault",
2839
+ "info",
2840
+ "contract-happy-path-only",
2841
+ `${bare.length} of ${withScenarios.length} contracts with scenarios have no scenario marked (edge) or (failure): ${bare.join(", ")}.`,
2842
+ s.rel,
2843
+ ),
2844
+ );
2845
+ }
2846
+ return out;
2847
+ }
2848
+
2811
2849
  // Scenario shape (Decision 3): a contract's scenario items need a WHEN (or
2812
- // GIVEN) and a THEN. That is the only reading of scenarios.
2850
+ // GIVEN) and a THEN.
2813
2851
  export function checkScenarioShape(artifacts) {
2814
2852
  const out = [];
2815
2853
  for (const s of artifacts.filter((a) => a.kind === "spec")) {
@@ -3850,6 +3888,7 @@ export function runVaultChecks(
3850
3888
  () => checkLifecycleGates(artifacts, vaultCfg),
3851
3889
  () => checkSpecAliases(artifacts),
3852
3890
  () => checkScenarioShape(artifacts),
3891
+ () => checkHappyPathOnly(artifacts),
3853
3892
  () => {
3854
3893
  const scan = scanCitations(cfg, artifacts, proj, grep);
3855
3894
  return [...checkContractCitations(artifacts, scan), ...checkEvidenceCitations(artifacts, vaultCfg, scan)];
@@ -13,6 +13,7 @@ tags: {{tags}}
13
13
  code_refs: []
14
14
  specs: []
15
15
  # no_spec: "<warum diese Story keine Spec braucht>" (wird am Plan-Gate geschrieben)
16
+ # variant: test (eine Test-Story; ihr Guide ist story.test)
16
17
  started_at: null
17
18
  closed_at: null
18
19
  plan_updated_at: null
@@ -13,6 +13,7 @@ tags: {{tags}}
13
13
  code_refs: []
14
14
  specs: []
15
15
  # no_spec: "<why this story needs no spec>" (written at the plan gate)
16
+ # variant: test (a test story; its guide is story.test)
16
17
  started_at: null
17
18
  closed_at: null
18
19
  plan_updated_at: null
@@ -13,6 +13,7 @@ tags: {{tags}}
13
13
  code_refs: []
14
14
  specs: []
15
15
  # no_spec: "<por qué esta historia no necesita spec>" (se escribe en el plan gate)
16
+ # variant: test (una historia de pruebas; su guía es story.test)
16
17
  started_at: null
17
18
  closed_at: null
18
19
  plan_updated_at: null
@@ -13,6 +13,7 @@ tags: {{tags}}
13
13
  code_refs: []
14
14
  specs: []
15
15
  # no_spec: "<pourquoi cette story n'a pas besoin de spec>" (écrit au plan gate)
16
+ # variant: test (une story de test ; son guide est story.test)
16
17
  started_at: null
17
18
  closed_at: null
18
19
  plan_updated_at: null
@@ -13,6 +13,7 @@ tags: {{tags}}
13
13
  code_refs: []
14
14
  specs: []
15
15
  # no_spec: "<почему этой стори не нужна спека>" (пишется на plan gate)
16
+ # variant: test (тестовая стори; её гайд — story.test)
16
17
  started_at: null
17
18
  closed_at: null
18
19
  plan_updated_at: null
@@ -13,6 +13,7 @@ tags: {{tags}}
13
13
  code_refs: []
14
14
  specs: []
15
15
  # no_spec: "<为什么这个故事不需要 spec>" (在 plan gate 写入)
16
+ # variant: test (测试故事;其 guide 为 story.test)
16
17
  started_at: null
17
18
  closed_at: null
18
19
  plan_updated_at: null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectstore-claude",
3
- "version": "0.30.10",
3
+ "version": "0.30.11",
4
4
  "description": "projectstore for Claude Code: the core pinned and bundled, the harness fixed. A release artifact; the way in is npx projectstore setup",
5
5
  "keywords": [
6
6
  "projectstore",
@@ -37,7 +37,7 @@
37
37
  "README.md"
38
38
  ],
39
39
  "dependencies": {
40
- "projectstore": "=0.30.10"
40
+ "projectstore": "=0.30.11"
41
41
  },
42
42
  "bundleDependencies": [
43
43
  "projectstore"