@ikunin/sprintpilot 2.2.28 → 2.2.29

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.
@@ -96,30 +96,101 @@ function readStateFile(fs, filePath) {
96
96
  // Narrow YAML parser sufficient for our write shape (the same shape we
97
97
  // produce via dumpYaml above). We deliberately avoid js-yaml so we don't
98
98
  // pull a runtime dep into the install-time script bundle.
99
+ //
100
+ // Supports:
101
+ // key: scalar (inline scalar)
102
+ // key: [a, b] (inline JSON array via parseScalar)
103
+ // key: (nested object — children at deeper indent)
104
+ // subkey: value
105
+ // key: (nested array — `- item` lines at deeper indent)
106
+ // - item-scalar
107
+ // - item-key: item-value
108
+ //
109
+ // The block-form array path was added in v2.2.29 — pre-2.2.29 the
110
+ // parser unconditionally `continue`d on any line without `:`, silently
111
+ // dropping every `- item` entry. Hand-edited state files (or any
112
+ // roundtrip through a tool that emits block-form YAML) lost their
113
+ // `story_queue`, leaving the autopilot's queue mysteriously empty.
99
114
  function parseYamlNarrow(text) {
100
115
  if (!text) return {};
101
116
  const lines = text.split(/\r?\n/);
102
117
  const root = {};
103
- const stack = [{ indent: -1, obj: root }];
118
+ // Stack frame:
119
+ // indent — indent of the KEY that opened this container (its
120
+ // children live at indent > frame.indent)
121
+ // container — the object or array we're populating
122
+ // isArray — true once we've promoted container from {} to []
123
+ // parentObj — owner of container (used to swap {} → [] when the
124
+ // first child is a `- ` line)
125
+ // parentKey — slot on parentObj that holds container
126
+ const stack = [{ indent: -1, container: root, isArray: false, parentObj: null, parentKey: null }];
104
127
  for (const raw of lines) {
105
128
  const hashIdx = raw.indexOf('#');
106
129
  const line = hashIdx === -1 ? raw : raw.slice(0, hashIdx);
107
130
  if (!line.trim()) continue;
108
131
  const indent = line.match(/^( *)/)[1].length;
109
132
  const content = line.slice(indent).trimEnd();
133
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) stack.pop();
134
+ const top = stack[stack.length - 1];
135
+
136
+ // List item shape: `- ` or bare `-`.
137
+ if (content === '-' || content.startsWith('- ')) {
138
+ // Promote container to array if this is the first list item seen
139
+ // for the current key. Root-level lists aren't supported (state
140
+ // files always have an object root) — skip cleanly.
141
+ if (!top.isArray) {
142
+ if (!top.parentObj || top.parentKey == null) continue;
143
+ const arr = [];
144
+ top.parentObj[top.parentKey] = arr;
145
+ top.container = arr;
146
+ top.isArray = true;
147
+ }
148
+ const rest = content === '-' ? '' : content.slice(2).trim();
149
+ if (rest === '') {
150
+ // Bare `-` with children below — append a fresh object and let
151
+ // subsequent deeper-indent lines populate it.
152
+ const child = {};
153
+ top.container.push(child);
154
+ stack.push({ indent, container: child, isArray: false, parentObj: null, parentKey: null });
155
+ continue;
156
+ }
157
+ const colon = rest.indexOf(':');
158
+ if (colon === -1) {
159
+ // Plain scalar list item.
160
+ top.container.push(parseScalar(rest));
161
+ continue;
162
+ }
163
+ // `- key: value` or `- key:` (object item).
164
+ const k = rest.slice(0, colon).trim();
165
+ const v = rest.slice(colon + 1).trim();
166
+ if (v === '') {
167
+ const child = {};
168
+ const wrapper = { [k]: child };
169
+ top.container.push(wrapper);
170
+ stack.push({ indent, container: child, isArray: false, parentObj: wrapper, parentKey: k });
171
+ } else {
172
+ top.container.push({ [k]: parseScalar(v) });
173
+ }
174
+ continue;
175
+ }
176
+
177
+ // Object key: value
110
178
  const colon = content.indexOf(':');
111
179
  if (colon === -1) continue;
112
180
  const key = content.slice(0, colon).trim();
113
181
  const rest = content.slice(colon + 1).trim();
114
- while (stack.length > 1 && stack[stack.length - 1].indent >= indent) stack.pop();
115
- const parent = stack[stack.length - 1].obj;
182
+ if (top.isArray) {
183
+ // Defensive: a stray `key:` inside an array context is malformed.
184
+ // Skip rather than corrupt the array.
185
+ continue;
186
+ }
116
187
  if (rest === '') {
117
188
  const child = {};
118
- parent[key] = child;
119
- stack.push({ indent, obj: child });
189
+ top.container[key] = child;
190
+ stack.push({ indent, container: child, isArray: false, parentObj: top.container, parentKey: key });
120
191
  continue;
121
192
  }
122
- parent[key] = parseScalar(rest);
193
+ top.container[key] = parseScalar(rest);
123
194
  }
124
195
  return root;
125
196
  }
@@ -1,6 +1,6 @@
1
1
  addon:
2
2
  name: sprintpilot
3
- version: 2.2.28
3
+ version: 2.2.29
4
4
  description: Sprintpilot — autopilot and multi-agent addon for BMad Method (git workflow, parallel agents, autonomous story execution)
5
5
  bmad_compatibility: ">=6.2.0"
6
6
  modules:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikunin/sprintpilot",
3
- "version": "2.2.28",
3
+ "version": "2.2.29",
4
4
  "description": "Sprintpilot — autopilot and multi-agent addon for BMad Method v6: git workflow, parallel agents, autonomous story execution",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {