shapeup-sdlc 3.9.0 → 3.9.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shapeup-sdlc-plugin",
3
3
  "displayName": "ShapeUp SDLC Plugin",
4
- "version": "3.9.0",
4
+ "version": "3.9.1",
5
5
  "description": "Shape Up SDLC harness for Claude Code: shaping, intake, orient, scope-mapping, building (T0-verified, sandboxed, scope-contracted), evaluation and QA skills orchestrated by a tech-lead.",
6
6
  "author": {
7
7
  "name": "Liberty Nguyen",
@@ -126,7 +126,9 @@ const IMPORT_RE = /(?:\bimport\b[^'"]*?from\s*|\bimport\s*|\bexport\b[^'"]*?from
126
126
  * walk uses, plus what each widening contributed (reported, so a reader can see why it resolved).
127
127
  */
128
128
  export function sourceExtensions(profile, entryPoint) {
129
- const raw = profile?.source_extensions ?? profile?.module_extensions ?? null;
129
+ // One spelling, the one the schema declares. A silent alias is a field nobody documented and
130
+ // nobody can be told to write.
131
+ const raw = profile?.source_extensions ?? null;
130
132
  const list = Array.isArray(raw) ? raw : typeof raw === "string" ? raw.split(/[,\s]+/) : [];
131
133
  const declared = list
132
134
  .map((e) => String(e).trim())
@@ -292,7 +294,6 @@ function sourceFilesUnder(root, exts) {
292
294
  let entries;
293
295
  try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
294
296
  for (const e of entries) {
295
- if (e.name.startsWith(".") && e.name !== ".") { if (SKIP_DIRS.has(e.name)) continue; }
296
297
  if (SKIP_DIRS.has(e.name)) continue;
297
298
  const abs = join(dir, e.name);
298
299
  if (e.isDirectory()) walk(abs);
@@ -335,8 +336,10 @@ export function scopeReachability(contracts, reachable, sources) {
335
336
  * @param {{cwd:string, gate?:boolean}} opts - cwd (root the SHARED/LOCAL paths resolve against),
336
337
  * gate (records mode; the CLI, not this function, turns gate+red into a non-zero exit).
337
338
  * @returns {{report:object, mermaid:(string|null)}} The trace report (covers_closure, reachability,
338
- * findings[], overall "green"|"red") and a Mermaid view when a wiring map exists. Each arm
339
- * self-skips (checked=false) when its artifact is absent — non-regression on pre-spine specs.
339
+ * scope_reachability, findings[], overall "green"|"red") and a Mermaid view when a wiring map
340
+ * exists. Each arm self-skips (checked=false) when its artifact is absent — non-regression on
341
+ * pre-spine specs — and reachability also self-skips when it cannot root its walk, which is what
342
+ * skips the per-scope arm with it.
340
343
  */
341
344
  export function traceLint(slug, { cwd, gate = false }) {
342
345
  const shared = sharedRoot(cwd, slug);
@@ -491,7 +494,8 @@ export function traceLint(slug, { cwd, gate = false }) {
491
494
  skipped_reason: `no engine is reachable from entry_point "${entryPoint}", which reaches ${reachable.size} file(s) — with no reachable engine as a control this walk cannot tell an orphaned module from an entry point that is not the runtime root (declarative routing, a manifest, a string-loaded screen). Reachability skipped.` };
492
495
  findings.push({ severity: "warn", code: "REACH-NO-CONTROL", message:
493
496
  `reachability did not run: all ${engines.length} engine(s) are unreachable from entry_point "${entryPoint}", which reaches ${reachable.size} file(s). ` +
494
- "Every engine orphaned is the one result this arm cannot distinguish from a wrong root — if this project wires its screens by name rather than by import, declare the module that does the wiring as the entry point." });
497
+ "Every engine orphaned is the one result this arm cannot distinguish from a wrong root. If some module does compose the app, declare that one as the entry point. " +
498
+ "If the screens are registered in a manifest instead — a route map, a plugin table — then no entry point roots this walk and unchecked is the correct end state for this project, not a profile to fix." });
495
499
  } else {
496
500
  for (const u of unreachable) {
497
501
  findings.push({ severity: "red", code: "UC-UNREACHABLE", uc: u.use_case,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shapeup-sdlc",
3
- "version": "3.9.0",
3
+ "version": "3.9.1",
4
4
  "description": "Shape Up for coding agents \u2014 with gates the agent can't talk its way past. Harness for Claude Code.",
5
5
  "bin": {
6
6
  "shapeup-sdlc": "bin/init.mjs"
@@ -224,10 +224,12 @@ Do NOT enter MAP SCOPES until Orient is accepted.
224
224
  archetype ∈ {client-only-game|web-service|mobile|library|data-pipeline}; entry_point is the
225
225
  reachability seam (a game's main.js is NOT a service's src/server.ts). Validate the enum — a
226
226
  typo must fail, not silently disable the check. entry_point is also the root reachability walks
227
- the import graph from, so pick the module the app's screens hang off, not merely the file the
228
- platform starts: a framework that registers screens by name (a route map, a manifest, a
229
- string-loaded page) leaves its start file importing nothing the feature touches, and the arm
230
- then reports that it could not check rather than calling every engine orphaned.
227
+ the import graph from, so where some module does compose the app, name that one rather than the
228
+ file the platform happens to start. Where the screens are registered in a MANIFEST instead (a
229
+ route map, a plugin table, a string-loaded page), no entry point roots an import walk at all —
230
+ measured on a real ArkTS app, where the navigation host imports nothing and every screen arrives
231
+ through route_map.json. The arm then reports that it could not check, and that is the correct
232
+ end state for such a project rather than a profile to keep re-declaring.
231
233
  source_extensions is optional and only needed when the project's modules end in something the
232
234
  entry point does not (e.g. [".ets"] when the entry is a .ts file); leaving it out costs a
233
235
  skipped arm, never a false red. The two probes feed the round build gate (`harness verify
@@ -1800,6 +1800,13 @@ while (verdict !== "pass" && round <= maxRounds) {
1800
1800
  `${gate.detail ? `: ${gate.detail}` : ""}). Treated as undeclared, not as green.`);
1801
1801
  }
1802
1802
 
1803
+ // Trace-lint runs a SECOND time here, and this is the pass that can see anything. At L1b the
1804
+ // scopes' files do not exist yet, so per-scope reachability measures an empty set and its warning
1805
+ // cannot fire. After the round's build gate the code is on disk, which is exactly when "this
1806
+ // scope's fixture is green and none of its code is in the compiled set" becomes a checkable fact
1807
+ // — the shape that once left three scopes green while their files never compiled. Advisory, like
1808
+ // the L1b pass: it overwrites the same projection with a later and better-informed one.
1809
+ await advisory(`verify trace --slug ${slug} --quiet`, "Build", `trace-lint:r${round}`);
1803
1810
  await advisory(`reduce hill --slug ${slug}`, "Build", "hill-derive");
1804
1811
  {
1805
1812
  const g = await crossGate("L2", "Build", ["proceed", "ask", "abort"],