@sabaiway/agent-workflow-memory 4.7.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,33 @@ All notable changes to the memory substrate. Versions are this **package's** npm
4
4
  they are distinct from the **deployment-lineage** stamp written into a project's
5
5
  `docs/ai/.memory-version` (which tracks the shared `agent-workflow` lineage, head `3.0.0`).
6
6
 
7
+ ## 5.0.0 — the scenario floor: a contract can no longer pin NOTHING (AD-117)
8
+
9
+ The reader enforced a minimum on `## Out of scope` and none on `## Scenarios`. A `kind: spec`
10
+ document could therefore carry an EMPTY scenario section and pass everything — the reader, both
11
+ `spec-check` lanes, the gate row. Measured cost, from writing the layer's first real specs: three
12
+ stub specs would have satisfied every mechanical check of that slice and delivered nothing.
13
+
14
+ > ### ⚠ BREAKING — the reader refuses a document it used to accept
15
+ >
16
+ > `SPEC_SCHEMA.rules` gains ONE frozen rule, **`scenarios-empty`**, in table position between
17
+ > `scenario-path` and `out-of-scope`: a `## Scenarios` section carrying no scenario line is now an
18
+ > error. The rule list is a frozen ordered contract, so a consumer that deep-equals it sees a 34th
19
+ > id, and a deployment's spec gate can turn red with no edit of its own. The remedy is one line per
20
+ > scenario — `- S<N> <name> :: unbound` while no test pins it.
21
+
22
+ - **`*(empty)*` is deliberately NOT an escape here.** Written under `## Scenarios` it still refuses,
23
+ as `scenario-line`, and a fixture now pins that. The asymmetry with `## Out of scope` (where the
24
+ marker IS the decision) is the point: an empty exclusion list is a claim, an empty scenario list is
25
+ an absence, and the grammar already carries the honest form for the absence.
26
+ - **`unbound` accepts on every status** — `draft`, `live` and `retired` alike — so the floor costs a
27
+ spec nothing but the statement that a scenario exists.
28
+ - Measured before shipping: no document in the fixture corpus, the live store or the shipped
29
+ templates carries an empty section, so the floor refuses nothing that already exists.
30
+ - The other 33 rule ids, every existing refusal and the whole `structure` extraction stay
31
+ behaviour-identical. The new arm was red-proofed against the pre-change reader.
32
+
33
+
7
34
  ## 4.7.0 — the reader's verdict gains the additive `structure` extraction (AD-114)
8
35
 
9
36
  The slice-2 checker (`spec-check`, next release) must read a document's structure through the SAME
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: agent-workflow-memory
3
3
  description: Deploy or upgrade a portable AI-agent memory substrate in any project — an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) and a structured `docs/ai/` context store with cap/archive/index enforcement. Use when the user wants to bootstrap `docs/ai/`, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-memory` / `/agent-workflow-memory upgrade`. Triggers on "set up the memory system", "deploy the AI memory here", "bootstrap docs/ai", "upgrade the memory substrate". This is the substrate only — the workflow methodology (plan→execute→review, queue, Cleanup) is owned elsewhere and injected into AGENTS.md by the family composition root.
4
4
  disable-model-invocation: true
5
5
  metadata:
6
- version: '4.7.0'
6
+ version: '5.0.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-memory
package/capability.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-memory",
5
5
  "kind": "memory-substrate",
6
- "version": "4.7.0",
6
+ "version": "5.0.0",
7
7
  "provides": ["context"],
8
8
  "roles": {},
9
9
  "detect": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-memory",
3
- "version": "4.7.0",
3
+ "version": "5.0.0",
4
4
  "description": "Portable, cross-agent memory substrate for AI coding agents — an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement, deployable standalone or as part of the agent-workflow family. The memory layer of the agent-workflow family.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -38,7 +38,7 @@ export const SPEC_SCHEMA = Object.freeze({
38
38
  'frontmatter', 'frontmatter-key', 'substrate-key', 'type', 'kind', 'maxlines', 'status', 'revision',
39
39
  'root-owns', 'slug', 'kind-path', 'root-uplink', 'title', 'section-missing', 'section-order',
40
40
  'section-forbidden', 'fence', 'children-link', 'children-duplicate', 'fan-out', 'scenario-line',
41
- 'scenario-number', 'scenario-marker', 'scenario-path', 'out-of-scope', 'module-line', 'module-empty',
41
+ 'scenario-number', 'scenario-marker', 'scenario-path', 'scenarios-empty', 'out-of-scope', 'module-line', 'module-empty',
42
42
  'module-traversal', 'module-absolute', 'module-backslash', 'module-glob', 'module-mix', 'parts',
43
43
  ]),
44
44
  });
@@ -238,6 +238,10 @@ const checkScenarios = (parsed, slug, status, errors, warnings) => {
238
238
  }
239
239
  scenarios.push(scenario);
240
240
  }
241
+ if (scenarios.length === 0) {
242
+ errors.push({ rule: 'scenarios-empty', message: `at least one scenario line (\`${SPEC_SCHEMA.unboundMarker}\` while no test pins it)` });
243
+ return;
244
+ }
241
245
  const gap = scenarios.findIndex((scenario, i) => scenario.n !== i + 1);
242
246
  if (gap !== -1) errors.push({ rule: 'scenario-number', message: `scenario ${gap + 1} is numbered S${scenarios[gap].n} — N runs contiguously from 1` });
243
247
  for (const scenario of scenarios) {
@@ -131,6 +131,14 @@ describe('readSpecDocument — refuse, exactly one rule per defect', () => {
131
131
  refuses(specDoc({ scenarios: ['- S1 a :: /abs/login.test.mjs :: spec:login/S1'] }), 'login.md', 'scenario-path');
132
132
  });
133
133
 
134
+ it('scenarios-empty / no empty-marker escape', () => {
135
+ refuses(specDoc({ scenarios: [] }), 'login.md', 'scenarios-empty');
136
+ refuses(specDoc({ scenarios: ['*(empty)*'] }), 'login.md', 'scenario-line');
137
+ for (const status of SPEC_SCHEMA.statuses) {
138
+ expect(readSpecDocument(specDoc({ scenarios: ['- S1 x :: unbound'], fields: { status } }), 'login.md').errors).toEqual([]);
139
+ }
140
+ });
141
+
134
142
  it('out-of-scope / module-empty / module-* path refusals / module-mix / parts', () => {
135
143
  refuses(specDoc({ outOfScope: '' }), 'login.md', 'out-of-scope');
136
144
  refuses(specDoc({ outOfScope: 'nothing excluded' }), 'login.md', 'out-of-scope');