@kaddo/cli 3.67.0 → 3.69.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.
Files changed (3) hide show
  1. package/dist/core.js +7471 -0
  2. package/dist/index.js +547 -43
  3. package/package.json +7 -2
package/dist/index.js CHANGED
@@ -1361,8 +1361,8 @@ function loadConfig(dir) {
1361
1361
  const parsed = configSchema.safeParse(raw ?? {});
1362
1362
  if (!parsed.success) {
1363
1363
  const issues = parsed.error.issues.map((i) => {
1364
- const path8 = i.path.join(".");
1365
- return path8 ? ` - ${path8}: ${i.message}` : ` - ${i.message}`;
1364
+ const path9 = i.path.join(".");
1365
+ return path9 ? ` - ${path9}: ${i.message}` : ` - ${i.message}`;
1366
1366
  }).join("\n");
1367
1367
  throw new ConfigError(`Invalid .kaddo/config.yml:
1368
1368
  ${issues}`);
@@ -2455,8 +2455,23 @@ The context pack and the Work Item (draft or candidate).
2455
2455
 
2456
2456
  ## Output
2457
2457
 
2458
- An improved Work Item with: problem, expected result, scope, out of scope, acceptance criteria,
2459
- validation (how to test it), definition of done, open questions and dependencies.
2458
+ An improved Work Item with: actor and outcome, current behavior, target behavior, entry points,
2459
+ end-to-end flow, impact analysis, module coverage, scope unknowns, scope confidence, problem,
2460
+ expected result, scope, out of scope, acceptance criteria (including end-to-end criteria for
2461
+ user-facing changes), validation (how to test it), definition of done, open questions and
2462
+ dependencies.
2463
+
2464
+ ## Steps
2465
+
2466
+ 1. **Outcome framing** \u2014 identify actor, current behavior, target behavior, observable completion.
2467
+ 2. **Journey reconstruction** \u2014 map entry point, interaction, service/API, state change, response,
2468
+ final outcome.
2469
+ 3. **Surface review** \u2014 evaluate: product/UI, frontend, backend, database, configuration, feature
2470
+ flags, content/copy, authentication/authorization, notifications, analytics, documentation,
2471
+ operations/release \u2014 as affected, reviewed-not-affected, unknown, or not-applicable.
2472
+ 4. **Module review** \u2014 for multirepo, evaluate each mapped module with the same statuses.
2473
+ 5. **Completeness review** \u2014 confirm: outcome covered, journey covered, modules assessed, unknowns
2474
+ visible, acceptance criteria end-to-end, scope and out-of-scope coherent.
2460
2475
 
2461
2476
  ## Rules
2462
2477
 
@@ -2464,14 +2479,23 @@ validation (how to test it), definition of done, open questions and dependencies
2464
2479
  - Do not expand scope without explicit confirmation.
2465
2480
  - Do not create mega Work Items \u2014 split when it covers multiple outcomes.
2466
2481
  - Keep acceptance criteria testable.
2482
+ - Include at least one end-to-end acceptance criterion for user-facing changes.
2483
+ - Do not reduce a product intent to the first technical implementation found.
2484
+ - Evaluate surfaces and modules before proposing files.
2467
2485
  - This skill refines scope and acceptance criteria but does not approve implementation readiness.
2468
2486
  Readiness requires human confirmation through \`kaddo ready\` or the MCP \`mark_work_item_ready\` action.
2469
2487
 
2470
2488
  ## Quality checklist
2471
2489
 
2490
+ - Actor and outcome are identified.
2491
+ - Current and target behavior are documented.
2492
+ - Journey is reconstructed for user-facing changes.
2493
+ - Surfaces and modules are evaluated with explicit statuses.
2472
2494
  - Problem and expected result are unambiguous.
2473
2495
  - Scope and out-of-scope are explicit.
2474
- - Acceptance criteria and validation are present and testable.
2496
+ - Acceptance criteria include end-to-end validation when applicable.
2497
+ - Scope unknowns are visible, not hidden.
2498
+ - Scope confidence is declared with reasons.
2475
2499
  - Open questions and dependencies are surfaced, not hidden.
2476
2500
 
2477
2501
  ## Example output
@@ -2692,14 +2716,19 @@ steps, and stop criteria (when to pause and ask).
2692
2716
  ## Rules
2693
2717
 
2694
2718
  - Do not start coding without confirmation.
2695
- - Do not expand scope.
2719
+ - Do not expand scope without updating the Work Item.
2696
2720
  - Never make commits or push \u2014 suggest only.
2721
+ - Review scope coverage before planning: check that expected result, current/target behavior,
2722
+ module coverage, and acceptance criteria are consistent. Flag contradictions (e.g., user-facing
2723
+ change with unassessed frontend module).
2697
2724
 
2698
2725
  ## Quality checklist
2699
2726
 
2727
+ - Pre-implementation scope review is documented.
2700
2728
  - Scope and expected files are explicit.
2701
2729
  - Risks and validations are listed.
2702
2730
  - Stop criteria are defined.
2731
+ - Module coverage aligns with planned changes.
2703
2732
 
2704
2733
  ## Example output
2705
2734
 
@@ -3863,18 +3892,31 @@ A refined Work Item intended to be saved under the lifecycle workspace:
3863
3892
 
3864
3893
  ## Instructions
3865
3894
 
3866
- 1. Restate the problem in one clear sentence.
3867
- 2. Split the candidate if it is too large for a single Work Item.
3868
- 3. Preserve the candidate's type (\`feature\`, \`bugfix\`, \`hotfix\`, \`spike\`, \`chore\`).
3869
- Keep \`chore\` for maintenance/tooling/config/infra work \u2014 never upgrade a chore to a feature.
3870
- 4. Validate the Knowledge Level (K0\u2013K4) and propose a different one if needed.
3871
- 5. Propose acceptance criteria.
3872
- 6. Propose an Out of scope section.
3873
- 7. Propose **how to test it** \u2014 concrete validation steps (commands to run, manual steps, or
3874
- test cases) that prove the change works once implemented. This is mandatory.
3875
- 8. Propose a Definition of Done.
3876
- 9. Identify open questions and assumptions.
3877
- 10. Suggest ownership candidates (code globs) if evident.
3895
+ 1. **Interpret the outcome** \u2014 what must change for the actor or consumer of this change.
3896
+ 2. **Identify the actor** \u2014 who experiences or triggers the change.
3897
+ 3. **Describe current behavior** \u2014 what happens today.
3898
+ 4. **Describe target behavior** \u2014 what should happen after the change.
3899
+ 5. **Reconstruct the journey** \u2014 for user-facing changes, map the end-to-end flow from entry
3900
+ point to final observable result before proposing files.
3901
+ 6. **Evaluate surfaces** \u2014 assess each potentially affected surface (frontend, backend, database,
3902
+ configuration, content, feature flags, authentication, notifications, analytics, documentation,
3903
+ operations) as \`affected\`, \`reviewed-not-affected\`, \`unknown\`, or \`not-applicable\`.
3904
+ 7. **Evaluate modules** \u2014 for multirepo projects, assess each plausibly related mapped module with
3905
+ the same statuses. A module must be \`affected\`, \`reviewed-not-affected\`, \`unknown\`, or
3906
+ \`not-applicable\`. Do not leave related modules unmentioned.
3907
+ 8. Restate the problem in one clear sentence.
3908
+ 9. Split the candidate if it is too large for a single Work Item.
3909
+ 10. Preserve the candidate's type (\`feature\`, \`bugfix\`, \`hotfix\`, \`spike\`, \`chore\`).
3910
+ Keep \`chore\` for maintenance/tooling/config/infra work \u2014 never upgrade a chore to a feature.
3911
+ 11. Validate the Knowledge Level (K0\u2013K4) and propose a different one if needed.
3912
+ 12. Propose acceptance criteria \u2014 include at least one end-to-end criterion for user-facing changes.
3913
+ 13. Propose an Out of scope section.
3914
+ 14. Propose **how to test it** \u2014 concrete validation steps (commands to run, manual steps, or
3915
+ test cases) that prove the change works once implemented. This is mandatory.
3916
+ 15. Propose a Definition of Done.
3917
+ 16. Identify open questions, assumptions, and scope unknowns.
3918
+ 17. Determine scope confidence (high, medium, low) with reasons.
3919
+ 18. Suggest ownership candidates (code globs) if evident.
3878
3920
 
3879
3921
  ## Constraints
3880
3922
 
@@ -3882,19 +3924,49 @@ A refined Work Item intended to be saved under the lifecycle workspace:
3882
3924
  - Do not invent business facts.
3883
3925
  - Do not assign a Knowledge Level higher than the change requires.
3884
3926
  - Mark assumptions explicitly.
3927
+ - Do not reduce a product intent to the first technical implementation found.
3928
+ - Inspect the observable outcome before proposing files.
3929
+ - For user-facing changes, assess the entry point, interaction surface, backend behavior, and
3930
+ final user-visible result.
3931
+ - For multirepo projects, assess each plausibly related mapped module.
3932
+ - Do not mark the Work Item ready while material scope remains unknown.
3933
+ - Ask focused questions instead of silently narrowing the request.
3934
+ - Preserve unsupported assumptions as assumptions.
3885
3935
 
3886
3936
  ## Output Format
3887
3937
 
3888
3938
  \`\`\`markdown
3889
3939
  # <Work Item title>
3890
3940
 
3941
+ **Actor and outcome:**
3942
+
3943
+ **Current behavior:**
3944
+
3945
+ **Target behavior:**
3946
+
3947
+ **Entry points:**
3948
+
3949
+ **End-to-end flow:**
3950
+
3891
3951
  **Problem:**
3892
3952
 
3893
3953
  **Expected result:**
3894
3954
 
3895
3955
  **Suggested Knowledge Level:** K1 / K2 / K3 / K4
3896
3956
 
3957
+ **Impact analysis:**
3958
+ <!-- surfaces: affected / reviewed-not-affected / unknown / not-applicable -->
3959
+
3960
+ **Module coverage:**
3961
+ <!-- for multirepo: each module as affected / reviewed-not-affected / unknown / not-applicable -->
3962
+
3963
+ **Scope unknowns:**
3964
+
3965
+ **Scope confidence:** high / medium / low
3966
+ <!-- reasons: -->
3967
+
3897
3968
  **Acceptance criteria:**
3969
+ <!-- include at least one end-to-end criterion for user-facing changes -->
3898
3970
 
3899
3971
  **Out of scope:**
3900
3972
 
@@ -4631,6 +4703,20 @@ Working code, tests and migrations, plus updated knowledge (ADR / capabilities /
4631
4703
  when the change affects them. You also produce a suggested branch name and a suggested
4632
4704
  Conventional Commit message \u2014 as suggestions, never executed.
4633
4705
 
4706
+ ## Pre-implementation Scope Review (VS-095)
4707
+
4708
+ Before implementing, review the Work Item's scope coverage:
4709
+ - Check that expected result, current behavior, and target behavior are documented.
4710
+ - Verify the journey covers the full user-facing flow when applicable.
4711
+ - Confirm affected surfaces and modules are declared.
4712
+ - Check module_coverage: if a mapped module is plausibly related but not assessed, flag it.
4713
+ - Verify acceptance criteria include end-to-end validation for user-facing changes.
4714
+ - Check for unresolved scope unknowns that could change the implementation.
4715
+
4716
+ If you find a contradiction (e.g., target behavior describes a user-facing registration flow but
4717
+ the mapped frontend module was not assessed), explain the finding, propose updating the Work Item,
4718
+ and wait for confirmation before proceeding. Do not silently expand scope.
4719
+
4634
4720
  ## Instructions
4635
4721
 
4636
4722
  1. **Suggest a branch first** (do not run it). Follow the Git strategy
@@ -6380,6 +6466,53 @@ async function getUntrackedFiles() {
6380
6466
 
6381
6467
  // src/services/artifact-reader.ts
6382
6468
  import matter from "gray-matter";
6469
+ var VALID_SCOPE_CONFIDENCE_LEVELS = /* @__PURE__ */ new Set(["high", "medium", "low"]);
6470
+ var VALID_COVERAGE_STATUSES = /* @__PURE__ */ new Set(["affected", "reviewed-not-affected", "unknown", "not-applicable"]);
6471
+ function parseScopeConfidence(data) {
6472
+ const sc = data.scope_confidence;
6473
+ if (!sc || typeof sc !== "object" || Array.isArray(sc)) return null;
6474
+ const obj = sc;
6475
+ const level = String(obj.level ?? "");
6476
+ if (!VALID_SCOPE_CONFIDENCE_LEVELS.has(level)) return null;
6477
+ const reasons = Array.isArray(obj.reasons) ? obj.reasons.map(String).filter(Boolean) : [];
6478
+ return { level, reasons };
6479
+ }
6480
+ function parseModuleCoverage(data) {
6481
+ const mc = data.module_coverage;
6482
+ if (!mc || typeof mc !== "object" || Array.isArray(mc)) return null;
6483
+ const result = {};
6484
+ for (const [id, val] of Object.entries(mc)) {
6485
+ if (!val || typeof val !== "object" || Array.isArray(val)) continue;
6486
+ const v = val;
6487
+ const status = String(v.status ?? "");
6488
+ if (!VALID_COVERAGE_STATUSES.has(status)) continue;
6489
+ result[id] = {
6490
+ status,
6491
+ ...v.reason ? { reason: String(v.reason) } : {},
6492
+ ...Array.isArray(v.evidence) ? { evidence: v.evidence.map(String).filter(Boolean) } : {}
6493
+ };
6494
+ }
6495
+ return Object.keys(result).length > 0 ? result : null;
6496
+ }
6497
+ function parseImpactAnalysis(data) {
6498
+ const ia = data.impact_analysis;
6499
+ if (!ia || typeof ia !== "object" || Array.isArray(ia)) return null;
6500
+ const surfaces = ia.surfaces;
6501
+ if (!surfaces || typeof surfaces !== "object" || Array.isArray(surfaces)) return null;
6502
+ const result = {};
6503
+ for (const [id, val] of Object.entries(surfaces)) {
6504
+ if (!val || typeof val !== "object" || Array.isArray(val)) continue;
6505
+ const v = val;
6506
+ const status = String(v.status ?? "");
6507
+ if (!VALID_COVERAGE_STATUSES.has(status)) continue;
6508
+ result[id] = {
6509
+ status,
6510
+ ...v.reason ? { reason: String(v.reason) } : {},
6511
+ ...v.question ? { question: String(v.question) } : {}
6512
+ };
6513
+ }
6514
+ return Object.keys(result).length > 0 ? result : null;
6515
+ }
6383
6516
  function parseArtifact(filePath, raw) {
6384
6517
  try {
6385
6518
  const { data } = matter(raw);
@@ -6408,7 +6541,10 @@ function parseArtifact(filePath, raw) {
6408
6541
  affectedModules: Array.isArray(data.affected_modules) ? data.affected_modules.map(String).filter(Boolean) : [],
6409
6542
  refinedBy: String(data.refined_by ?? ""),
6410
6543
  implementedBy: String(data.implemented_by ?? ""),
6411
- closedBy: String(data.closed_by ?? "")
6544
+ closedBy: String(data.closed_by ?? ""),
6545
+ scopeConfidence: parseScopeConfidence(data),
6546
+ moduleCoverage: parseModuleCoverage(data),
6547
+ impactAnalysis: parseImpactAnalysis(data)
6412
6548
  };
6413
6549
  } catch {
6414
6550
  return null;
@@ -6596,10 +6732,10 @@ function analyzeGuard(touchedFiles, artifacts, silentWithoutOwnership) {
6596
6732
  import { parse as parseYaml4, stringify as stringifyYaml2 } from "yaml";
6597
6733
  var IGNORE_FILE = ".kaddo/ignores.yml";
6598
6734
  function loadIgnores(dir) {
6599
- const path8 = join(dir, IGNORE_FILE);
6600
- if (!exists(path8)) return [];
6735
+ const path9 = join(dir, IGNORE_FILE);
6736
+ if (!exists(path9)) return [];
6601
6737
  try {
6602
- const raw = readFile(path8);
6738
+ const raw = readFile(path9);
6603
6739
  const parsed = parseYaml4(raw);
6604
6740
  return Array.isArray(parsed) ? parsed : [];
6605
6741
  } catch {
@@ -6607,7 +6743,7 @@ function loadIgnores(dir) {
6607
6743
  }
6608
6744
  }
6609
6745
  function saveIgnore(dir, entry) {
6610
- const path8 = join(dir, IGNORE_FILE);
6746
+ const path9 = join(dir, IGNORE_FILE);
6611
6747
  const existing = loadIgnores(dir);
6612
6748
  const idx = existing.findIndex((e) => e.artifact_id === entry.artifact_id);
6613
6749
  if (idx >= 0) {
@@ -6615,17 +6751,17 @@ function saveIgnore(dir, entry) {
6615
6751
  } else {
6616
6752
  existing.push(entry);
6617
6753
  }
6618
- writeFile(path8, stringifyYaml2(existing));
6754
+ writeFile(path9, stringifyYaml2(existing));
6619
6755
  }
6620
6756
  function isIgnored(ignores, artifactId) {
6621
6757
  return ignores.find((e) => e.artifact_id === artifactId);
6622
6758
  }
6623
6759
  function removeIgnore(dir, artifactId) {
6624
- const path8 = join(dir, IGNORE_FILE);
6760
+ const path9 = join(dir, IGNORE_FILE);
6625
6761
  const existing = loadIgnores(dir);
6626
6762
  const filtered = existing.filter((e) => e.artifact_id !== artifactId);
6627
6763
  if (filtered.length === existing.length) return false;
6628
- writeFile(path8, stringifyYaml2(filtered));
6764
+ writeFile(path9, stringifyYaml2(filtered));
6629
6765
  return true;
6630
6766
  }
6631
6767
 
@@ -6647,11 +6783,11 @@ function moduleArtifactCoverage(dir, id) {
6647
6783
  };
6648
6784
  }
6649
6785
  function loadMappedModules(dir) {
6650
- const path8 = join(dir, DESCRIPTOR_PATH);
6651
- if (!exists(path8)) return [];
6786
+ const path9 = join(dir, DESCRIPTOR_PATH);
6787
+ if (!exists(path9)) return [];
6652
6788
  let parsed;
6653
6789
  try {
6654
- parsed = parseYaml5(readFile(path8));
6790
+ parsed = parseYaml5(readFile(path9));
6655
6791
  } catch {
6656
6792
  return [];
6657
6793
  }
@@ -6969,6 +7105,142 @@ function analyzeCrossRepoEvidence(input) {
6969
7105
  };
6970
7106
  }
6971
7107
 
7108
+ // src/core/scope-coverage.ts
7109
+ var VALID_CONFIDENCE_LEVELS = /* @__PURE__ */ new Set(["high", "medium", "low"]);
7110
+ var VALID_COVERAGE_STATUSES2 = /* @__PURE__ */ new Set(["affected", "reviewed-not-affected", "unknown", "not-applicable"]);
7111
+ var USER_FACING_KEYWORDS_EN = [
7112
+ "user",
7113
+ "visitor",
7114
+ "page",
7115
+ "registration",
7116
+ "login",
7117
+ "form",
7118
+ "show",
7119
+ "display",
7120
+ "cta",
7121
+ "flow",
7122
+ "screen",
7123
+ "dashboard",
7124
+ "ui",
7125
+ "frontend",
7126
+ "interface",
7127
+ "button",
7128
+ "menu",
7129
+ "navigation",
7130
+ "modal",
7131
+ "dialog",
7132
+ "notification",
7133
+ "toast",
7134
+ "message"
7135
+ ];
7136
+ var USER_FACING_KEYWORDS_ES = [
7137
+ "usuario",
7138
+ "visitante",
7139
+ "p\xE1gina",
7140
+ "registro",
7141
+ "login",
7142
+ "formulario",
7143
+ "mostrar",
7144
+ "cta",
7145
+ "flujo",
7146
+ "pantalla",
7147
+ "interfaz",
7148
+ "bot\xF3n",
7149
+ "men\xFA",
7150
+ "navegaci\xF3n",
7151
+ "modal",
7152
+ "di\xE1logo",
7153
+ "notificaci\xF3n",
7154
+ "mensaje"
7155
+ ];
7156
+ var USER_FACING_KEYWORDS = /* @__PURE__ */ new Set([...USER_FACING_KEYWORDS_EN, ...USER_FACING_KEYWORDS_ES]);
7157
+ function analyzeScopeCoverage(artifact, registeredModuleIds, frontendModuleIds) {
7158
+ const findings = [];
7159
+ const id = artifact.id || artifact.title;
7160
+ const mc = artifact.moduleCoverage;
7161
+ const ia = artifact.impactAnalysis;
7162
+ const sc = artifact.scopeConfidence;
7163
+ const affectedModules = artifact.affectedModules;
7164
+ const lifecycle = artifact.status;
7165
+ const isCompleted = lifecycle === "completed" || lifecycle === "archived";
7166
+ const isReady = lifecycle === "ready";
7167
+ const hasScopeCoverage = mc !== null || ia !== null || sc !== null;
7168
+ const unknownModules = [];
7169
+ if (sc) {
7170
+ if (!VALID_CONFIDENCE_LEVELS.has(sc.level)) {
7171
+ findings.push({ id, severity: "blocking", message: `Invalid scope_confidence level "${sc.level}". Must be high, medium, or low.` });
7172
+ }
7173
+ if (isReady && sc.level === "low") {
7174
+ findings.push({ id, severity: "warning", message: "Work Item is ready but scope confidence is low." });
7175
+ }
7176
+ }
7177
+ if (mc) {
7178
+ for (const modId of Object.keys(mc)) {
7179
+ if (modId === "core") continue;
7180
+ if (!registeredModuleIds.includes(modId)) {
7181
+ findings.push({ id, severity: "blocking", message: `Module "${modId}" in module_coverage is not registered in .kaddo/modules.yml.` });
7182
+ }
7183
+ }
7184
+ for (const [modId, entry] of Object.entries(mc)) {
7185
+ if (!VALID_COVERAGE_STATUSES2.has(entry.status)) {
7186
+ findings.push({ id, severity: "blocking", message: `Invalid module_coverage status "${entry.status}" for module "${modId}".` });
7187
+ }
7188
+ if (entry.status === "affected" && !entry.reason) {
7189
+ findings.push({ id, severity: "warning", message: `Module "${modId}" is affected but has no reason.` });
7190
+ }
7191
+ if (entry.status === "unknown") {
7192
+ unknownModules.push(modId);
7193
+ }
7194
+ }
7195
+ for (const [modId, entry] of Object.entries(mc)) {
7196
+ if (entry.status === "affected" && !affectedModules.includes(modId)) {
7197
+ findings.push({ id, severity: "blocking", message: `Module "${modId}" is marked affected in module_coverage but missing from affected_modules.` });
7198
+ }
7199
+ }
7200
+ for (const modId of affectedModules) {
7201
+ const entry = mc[modId];
7202
+ if (entry && entry.status !== "affected") {
7203
+ findings.push({ id, severity: "blocking", message: `Module "${modId}" is in affected_modules but module_coverage status is "${entry.status}", not "affected".` });
7204
+ }
7205
+ }
7206
+ if (isReady && unknownModules.length > 0) {
7207
+ for (const modId of unknownModules) {
7208
+ findings.push({ id, severity: "warning", message: `Work Item is ready but module "${modId}" scope remains unknown.` });
7209
+ }
7210
+ }
7211
+ }
7212
+ if (ia) {
7213
+ for (const [surface, entry] of Object.entries(ia)) {
7214
+ if (!VALID_COVERAGE_STATUSES2.has(entry.status)) {
7215
+ findings.push({ id, severity: "blocking", message: `Invalid impact_analysis status "${entry.status}" for surface "${surface}".` });
7216
+ }
7217
+ if (entry.status === "unknown" && !entry.question && !entry.reason) {
7218
+ findings.push({ id, severity: "warning", message: `Surface "${surface}" is unknown but has no question or explanation.` });
7219
+ }
7220
+ }
7221
+ }
7222
+ if (!isCompleted && frontendModuleIds.length > 0) {
7223
+ const wiText = `${artifact.title} ${artifact.rawFrontmatter?.target_behavior ?? ""} ${artifact.rawFrontmatter?.current_behavior ?? ""}`.toLowerCase();
7224
+ const isUserFacing = [...USER_FACING_KEYWORDS].some((kw) => wiText.includes(kw));
7225
+ if (isUserFacing && mc) {
7226
+ for (const fid of frontendModuleIds) {
7227
+ if (!mc[fid]) {
7228
+ findings.push({ id, severity: "warning", message: `A mapped user-facing module "${fid}" was not assessed in module_coverage.` });
7229
+ }
7230
+ }
7231
+ }
7232
+ }
7233
+ return {
7234
+ id,
7235
+ scopeConfidence: sc,
7236
+ moduleCoverage: mc,
7237
+ impactAnalysis: ia,
7238
+ findings,
7239
+ unknownModules,
7240
+ hasScopeCoverage
7241
+ };
7242
+ }
7243
+
6972
7244
  // src/commands/guard.ts
6973
7245
  import path4 from "path";
6974
7246
  import { parse as parseYaml7 } from "yaml";
@@ -7259,7 +7531,7 @@ function buildGuardHistory(dir) {
7259
7531
  const d = hotspotDir(t.code_path);
7260
7532
  hotspotMap.set(d, (hotspotMap.get(d) ?? 0) + 1);
7261
7533
  }
7262
- const hotspots = [...hotspotMap.entries()].map(([path8, warnings]) => ({ path: path8, warnings })).sort((a, b) => b.warnings - a.warnings);
7534
+ const hotspots = [...hotspotMap.entries()].map(([path9, warnings]) => ({ path: path9, warnings })).sort((a, b) => b.warnings - a.warnings);
7263
7535
  return {
7264
7536
  available: true,
7265
7537
  total_runs: sorted.length,
@@ -7630,6 +7902,27 @@ async function runGuard(opts = {}) {
7630
7902
  console.log("");
7631
7903
  }
7632
7904
  }
7905
+ const mappedModules = loadMappedModules(dir);
7906
+ const registeredModuleIdsForScope = mappedModules.map((m) => m.id);
7907
+ const frontendModuleIds = mappedModules.filter((m) => m.type === "frontend" || m.type === "web" || m.type === "mobile" || m.type === "ui").map((m) => m.id);
7908
+ const wiArtifactsForScope = artifacts.filter((a) => a.isWorkItem);
7909
+ const scopeSummaries = [];
7910
+ for (const wi of wiArtifactsForScope) {
7911
+ if (!wi.scopeConfidence && !wi.moduleCoverage && !wi.impactAnalysis) continue;
7912
+ const summary = analyzeScopeCoverage(wi, registeredModuleIdsForScope, frontendModuleIds);
7913
+ if (summary.findings.length > 0) scopeSummaries.push(summary);
7914
+ }
7915
+ if (scopeSummaries.length > 0) {
7916
+ console.log("Scope coverage:");
7917
+ for (const s of scopeSummaries) {
7918
+ console.log(` ${s.id}:`);
7919
+ for (const f of s.findings) {
7920
+ const icon = f.severity === "blocking" ? "\u2717" : f.severity === "warning" ? "!" : "\xB7";
7921
+ console.log(` ${icon} ${f.message}`);
7922
+ }
7923
+ }
7924
+ console.log("");
7925
+ }
7633
7926
  const ownerMap = loadOwners(dir);
7634
7927
  const matchedDomains = collectMatchedDomains(activeMatches.map((m) => m.artifact.domains));
7635
7928
  const affectedOwners = resolveAffectedOwners(matchedDomains, ownerMap);
@@ -7907,12 +8200,44 @@ domains: []
7907
8200
  capabilities: []
7908
8201
  code: []
7909
8202
  created_at: YYYY-MM-DD
8203
+ # scope_confidence:
8204
+ # level: medium
8205
+ # reasons: []
8206
+ # module_coverage:
8207
+ # core:
8208
+ # status: affected
8209
+ # reason: ""
8210
+ # impact_analysis:
8211
+ # surfaces:
8212
+ # frontend:
8213
+ # status: affected
8214
+ # reason: ""
7910
8215
  ---
7911
8216
 
7912
8217
  # <Title>
7913
8218
 
7914
8219
  > Type: feature \xB7 Level: K2
7915
8220
 
8221
+ ## Actor and outcome
8222
+
8223
+ _Who experiences the change and what is the observable result?_
8224
+
8225
+ ## Current behavior
8226
+
8227
+ _What happens today?_
8228
+
8229
+ ## Target behavior
8230
+
8231
+ _What should happen after this change?_
8232
+
8233
+ ## Entry points
8234
+
8235
+ _Where does the flow start for the actor?_
8236
+
8237
+ ## End-to-end flow
8238
+
8239
+ _Describe the complete flow from entry point to final result._
8240
+
7916
8241
  ## Problem
7917
8242
 
7918
8243
  _What problem or opportunity does this address?_
@@ -7921,9 +8246,26 @@ _What problem or opportunity does this address?_
7921
8246
 
7922
8247
  _What should be true once this is done?_
7923
8248
 
8249
+ ## Impact analysis
8250
+
8251
+ _Which surfaces are affected, reviewed-not-affected, unknown, or not-applicable?_
8252
+
8253
+ ## Module coverage
8254
+
8255
+ _For multirepo: which modules were evaluated and their status._
8256
+
8257
+ ## Scope unknowns
8258
+
8259
+ _What remains unknown? Include focused questions._
8260
+
8261
+ ## Scope confidence
8262
+
8263
+ _high / medium / low \u2014 with reasons._
8264
+
7924
8265
  ## Acceptance criteria
7925
8266
 
7926
8267
  - [ ] ...
8268
+ - [ ] _Include at least one end-to-end criterion for user-facing changes._
7927
8269
 
7928
8270
  ## Design
7929
8271
 
@@ -7955,6 +8297,9 @@ ${QUALITY}
7955
8297
  - [ ] Title is specific and action-oriented.
7956
8298
  - [ ] \`code:\` globs declared so Guard can relate changes.
7957
8299
  - [ ] Knowledge level matches the real uncertainty.
8300
+ - [ ] Actor and outcome are identified.
8301
+ - [ ] Current and target behavior are documented.
8302
+ - [ ] Scope confidence is declared.
7958
8303
  `;
7959
8304
  var ROADMAP = `---
7960
8305
  type: roadmap
@@ -9757,10 +10102,10 @@ function slugify2(name) {
9757
10102
  return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9758
10103
  }
9759
10104
  function readModulesDescriptor(dir) {
9760
- const path8 = join(dir, DESCRIPTOR_PATH2);
9761
- if (!exists(path8)) return { version: 1, modules: [] };
10105
+ const path9 = join(dir, DESCRIPTOR_PATH2);
10106
+ if (!exists(path9)) return { version: 1, modules: [] };
9762
10107
  try {
9763
- const parsed = parseYaml9(readFile(path8));
10108
+ const parsed = parseYaml9(readFile(path9));
9764
10109
  return { version: parsed.version ?? 1, modules: parsed.modules ?? [] };
9765
10110
  } catch {
9766
10111
  return { version: 1, modules: [] };
@@ -10140,7 +10485,7 @@ function sectionParagraph(md, title) {
10140
10485
  }
10141
10486
  return "";
10142
10487
  }
10143
- function parseCapsule(id, path8, md) {
10488
+ function parseCapsule(id, path9, md) {
10144
10489
  const { data } = matter3(md);
10145
10490
  const updatedAt = data.updated_at ? String(data.updated_at) : void 0;
10146
10491
  let ageDays = null;
@@ -10150,7 +10495,7 @@ function parseCapsule(id, path8, md) {
10150
10495
  }
10151
10496
  return {
10152
10497
  id,
10153
- path: path8,
10498
+ path: path9,
10154
10499
  system: data.system ? String(data.system) : id,
10155
10500
  owner: data.owner ? String(data.owner) : void 0,
10156
10501
  updatedAt,
@@ -10236,9 +10581,9 @@ function buildGraph(dir, config, opts = {}, now = /* @__PURE__ */ new Date()) {
10236
10581
  ];
10237
10582
  const presentLayers = [];
10238
10583
  for (const layer2 of layerDocs) {
10239
- const path8 = layer2.files.map((f) => `${KNOWLEDGE3}/${f}`).find((rel) => exists(join(dir, rel)));
10240
- if (path8) {
10241
- addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path8 });
10584
+ const path9 = layer2.files.map((f) => `${KNOWLEDGE3}/${f}`).find((rel) => exists(join(dir, rel)));
10585
+ if (path9) {
10586
+ addNode({ id: layer2.id, type: layer2.type, label: layer2.label, path: path9 });
10242
10587
  presentLayers.push(layer2.id);
10243
10588
  }
10244
10589
  }
@@ -11774,10 +12119,10 @@ function resolveNextStep(dir, now = /* @__PURE__ */ new Date()) {
11774
12119
  const q = (rel) => analyzeKnowledgeArtifact(dir, rel);
11775
12120
  const resolveAgent = (agent) => {
11776
12121
  const file = agent.endsWith(".md") ? agent : `${agent}.md`;
11777
- const path8 = agentInstallPath(file);
11778
- const installed = isFile(join(dir, path8));
12122
+ const path9 = agentInstallPath(file);
12123
+ const installed = isFile(join(dir, path9));
11779
12124
  const group = agentGroupOf(file);
11780
- return { agentPath: path8, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
12125
+ return { agentPath: path9, agentInstalled: installed, installCommand: installed ? void 0 : `kaddo add agents --group ${group}` };
11781
12126
  };
11782
12127
  const moduleRepo = isModule(config);
11783
12128
  const coreRepo = isCore(config);
@@ -13250,6 +13595,16 @@ function buildProjectExplanation(dir) {
13250
13595
  releaseReady: completed.filter((a) => a.releaseStatus === "ready" || a.releaseStatus === "released").length
13251
13596
  };
13252
13597
  })(),
13598
+ scopeCoverage: workItemArtifacts.filter((a) => a.scopeConfidence || a.moduleCoverage || a.impactAnalysis).map((a) => {
13599
+ const unknownModules = a.moduleCoverage ? Object.entries(a.moduleCoverage).filter(([, v]) => v.status === "unknown").map(([k]) => k) : [];
13600
+ return {
13601
+ id: a.id || a.title,
13602
+ scopeConfidence: a.scopeConfidence,
13603
+ moduleCoverage: a.moduleCoverage,
13604
+ unknownModules,
13605
+ hasScopeCoverage: true
13606
+ };
13607
+ }),
13253
13608
  implementationEvidence: workItemArtifacts.filter((a) => a.affectedModules.length > 0).map((a) => {
13254
13609
  const fm2 = a.rawFrontmatter;
13255
13610
  const evidence = fm2.implementation_evidence;
@@ -13430,6 +13785,23 @@ function renderExplanationHuman(exp) {
13430
13785
  }
13431
13786
  lines.push("");
13432
13787
  }
13788
+ if (exp.scopeCoverage.length > 0) {
13789
+ lines.push("## Work Item Scope Coverage");
13790
+ for (const sc of exp.scopeCoverage) {
13791
+ lines.push("");
13792
+ lines.push(`### ${sc.id}`);
13793
+ if (sc.scopeConfidence) lines.push(`- Scope confidence: ${sc.scopeConfidence.level}`);
13794
+ if (sc.moduleCoverage) {
13795
+ const affected = Object.entries(sc.moduleCoverage).filter(([, v]) => v.status === "affected").map(([k]) => k);
13796
+ const reviewed = Object.entries(sc.moduleCoverage).filter(([, v]) => v.status === "reviewed-not-affected").map(([k]) => k);
13797
+ const unknown = Object.entries(sc.moduleCoverage).filter(([, v]) => v.status === "unknown").map(([k]) => k);
13798
+ if (affected.length > 0) lines.push(`Affected: ${affected.join(", ")}`);
13799
+ if (reviewed.length > 0) lines.push(`Reviewed, not affected: ${reviewed.join(", ")}`);
13800
+ if (unknown.length > 0) lines.push(`Unknown: ${unknown.join(", ")}`);
13801
+ }
13802
+ }
13803
+ lines.push("");
13804
+ }
13433
13805
  if (exp.domains.length > 0) {
13434
13806
  lines.push("## Domains");
13435
13807
  lines.push(`- ${exp.domains.join(", ")}`);
@@ -13918,6 +14290,13 @@ function toContextWorkItem(a) {
13918
14290
  wi.validationStatus = a.validationStatus || (isHistorical ? "not-assessed" : void 0);
13919
14291
  wi.releaseStatus = a.releaseStatus || (isHistorical ? "not-assessed" : void 0);
13920
14292
  if (a.affectedModules.length > 0) wi.affectedModules = a.affectedModules;
14293
+ if (a.scopeConfidence) wi.scopeConfidence = a.scopeConfidence;
14294
+ if (a.moduleCoverage) wi.moduleCoverage = a.moduleCoverage;
14295
+ if (a.impactAnalysis) wi.impactAnalysis = a.impactAnalysis;
14296
+ if (a.moduleCoverage) {
14297
+ const unknowns = Object.entries(a.moduleCoverage).filter(([, v]) => v.status === "unknown").map(([k]) => k);
14298
+ if (unknowns.length > 0) wi.scopeUnknowns = unknowns;
14299
+ }
13921
14300
  return wi;
13922
14301
  }
13923
14302
  function toContextArtifact(a) {
@@ -15054,6 +15433,24 @@ function runUnderstand() {
15054
15433
  if (ds2.releaseReady > 0) console.log(` - Release ready: ${ds2.releaseReady}`);
15055
15434
  console.log(` - Current active work: ${ds2.activeWorkItems > 0 ? ds2.activeWorkItems : "none"}`);
15056
15435
  }
15436
+ if (exp.scopeCoverage.length > 0) {
15437
+ const withUnknowns = exp.scopeCoverage.filter((sc) => sc.unknownModules.length > 0);
15438
+ if (withUnknowns.length > 0) {
15439
+ console.log("");
15440
+ console.log("Scope unknowns:");
15441
+ for (const sc of withUnknowns) {
15442
+ console.log(` ${sc.id}: ${sc.unknownModules.join(", ")}`);
15443
+ }
15444
+ }
15445
+ const lowConfidence = exp.scopeCoverage.filter((sc) => sc.scopeConfidence?.level === "low");
15446
+ if (lowConfidence.length > 0) {
15447
+ console.log("");
15448
+ console.log("Low scope confidence:");
15449
+ for (const sc of lowConfidence) {
15450
+ console.log(` ${sc.id}`);
15451
+ }
15452
+ }
15453
+ }
15057
15454
  if (rec.skill) console.log(`Recommended skill: ${rec.skill}`);
15058
15455
  console.log(`Next step: ${rec.label}`);
15059
15456
  if (rec.reason) console.log(`Why: ${rec.reason}`);
@@ -18095,6 +18492,110 @@ async function runReady(id, opts = {}) {
18095
18492
  outro2(`Work Item ${wi.id} is ready.`);
18096
18493
  }
18097
18494
 
18495
+ // src/commands/admin.ts
18496
+ import path8 from "path";
18497
+ import { fileURLToPath as fileURLToPath2 } from "url";
18498
+ import net from "net";
18499
+ function isPortAvailable(port, host) {
18500
+ return new Promise((resolve) => {
18501
+ const server = net.createServer();
18502
+ server.once("error", () => resolve(false));
18503
+ server.once("listening", () => {
18504
+ server.close();
18505
+ resolve(true);
18506
+ });
18507
+ server.listen(port, host);
18508
+ });
18509
+ }
18510
+ function resolveStaticDir() {
18511
+ const __dirname = path8.dirname(fileURLToPath2(import.meta.url));
18512
+ const candidates = [
18513
+ path8.resolve(__dirname, "..", "..", "admin", "dist"),
18514
+ path8.resolve(__dirname, "..", "node_modules", "@kaddo", "admin", "dist")
18515
+ ];
18516
+ for (const c of candidates) {
18517
+ if (exists(join(c, "index.html"))) return c;
18518
+ }
18519
+ return null;
18520
+ }
18521
+ async function runAdmin(opts = {}) {
18522
+ const dir = cwd();
18523
+ const port = opts.port ?? 4173;
18524
+ const host = opts.host ?? "127.0.0.1";
18525
+ const config = loadConfig(dir);
18526
+ if (!config) {
18527
+ console.log("");
18528
+ console.log("Kaddo Admin could not start.");
18529
+ console.log("");
18530
+ console.log("No Kaddo project was detected in the current directory.");
18531
+ console.log("");
18532
+ console.log("Run:");
18533
+ console.log(" kaddo init");
18534
+ console.log("");
18535
+ console.log("or execute this command from a Kaddo project.");
18536
+ console.log("");
18537
+ process.exit(1);
18538
+ }
18539
+ const available = await isPortAvailable(port, host);
18540
+ if (!available) {
18541
+ console.log("");
18542
+ console.log(`Port ${port} is already in use.`);
18543
+ console.log("");
18544
+ console.log("Use:");
18545
+ console.log(` kaddo admin --port <port>`);
18546
+ console.log("");
18547
+ process.exit(1);
18548
+ }
18549
+ const staticDir = resolveStaticDir();
18550
+ if (!staticDir) {
18551
+ console.error("Admin frontend not built. Run `pnpm -r build` first.");
18552
+ process.exit(1);
18553
+ }
18554
+ const { createAdminServer, SQLiteAdminStorage } = await import("@kaddo/admin-server");
18555
+ const dbDir = join(dir, ".kaddo", "admin");
18556
+ ensureDir(dbDir);
18557
+ const dbPath = join(dbDir, "admin.db");
18558
+ const storage = new SQLiteAdminStorage(dbPath);
18559
+ await storage.initialize();
18560
+ const server = await createAdminServer({
18561
+ projectDir: dir,
18562
+ storage,
18563
+ staticDir,
18564
+ host,
18565
+ port
18566
+ });
18567
+ const address = await server.start();
18568
+ console.log("");
18569
+ console.log("Kaddo Admin");
18570
+ console.log("");
18571
+ console.log(`Project: ${config.project.name}`);
18572
+ console.log(`Mode: local`);
18573
+ console.log(`Address: ${address}`);
18574
+ console.log("");
18575
+ console.log(`Session: active`);
18576
+ console.log(`Storage: SQLite`);
18577
+ console.log(`Project source: Kaddo Core`);
18578
+ console.log("");
18579
+ console.log("Press Ctrl+C to stop.");
18580
+ console.log("");
18581
+ if (!opts.noOpen) {
18582
+ try {
18583
+ const { default: open } = await import("open");
18584
+ await open(address);
18585
+ } catch {
18586
+ }
18587
+ }
18588
+ const shutdown = async () => {
18589
+ console.log("");
18590
+ console.log("Shutting down...");
18591
+ await server.stop();
18592
+ console.log("Kaddo Admin stopped.");
18593
+ process.exit(0);
18594
+ };
18595
+ process.on("SIGINT", shutdown);
18596
+ process.on("SIGTERM", shutdown);
18597
+ }
18598
+
18098
18599
  // src/index.ts
18099
18600
  var require2 = createRequire(import.meta.url);
18100
18601
  var { version } = require2("../package.json");
@@ -18119,8 +18620,8 @@ var capsuleCmd = program.command("capsule").description("Export this project as
18119
18620
  capsuleCmd.command("export").description("Write a Knowledge Capsule about this project to .kaddo/exports/").option("--scope <scope>", 'Export scope: "system" includes all mapped modules').option("--module <id>", "Export a capsule for a specific mapped module").action((opts) => {
18120
18621
  runCapsuleExport(opts);
18121
18622
  });
18122
- capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path8) => {
18123
- runCapsuleAdd(path8);
18623
+ capsuleCmd.command("add <path>").description("Register an external Knowledge Capsule as project context (.kaddo/external.yml)").action((path9) => {
18624
+ runCapsuleAdd(path9);
18124
18625
  });
18125
18626
  var graphCmd = program.command("graph").description("Export the lightweight, file-based knowledge graph of the project");
18126
18627
  graphCmd.command("export").description("Write the knowledge graph to .kaddo/graph.json and .kaddo/graph.mmd").option("--scope <scope>", "Graph scope: active (default) or all").option("--format <format>", "Output format: json, mermaid (default: both)").action((opts) => {
@@ -18238,6 +18739,9 @@ modulesCmd.command("validate").description("Validate registered modules against
18238
18739
  program.command("add [module]").description("Install an optional Kaddo module (adr, incident, rfc, migration, legacy, agents, skills, standards, security, stack, git-strategy)").option("--all", "For `add agents` / `add skills`: install every item (not just the recommended set)").option("--group <name>", "For `add agents`: business|product|tech|delivery|utilities. For `add skills`: delivery|tech|integration").action((moduleName, opts) => {
18239
18740
  runAdd(moduleName ?? "", { all: opts.all, group: opts.group });
18240
18741
  });
18742
+ program.command("admin").description("Start Kaddo Admin \u2014 a local web interface for the project knowledge").option("--port <port>", "Server port (default: 4173)", parseInt).option("--host <host>", "Server host (default: 127.0.0.1)").option("--no-open", "Do not open the browser automatically").action(async (opts) => {
18743
+ await runAdmin({ port: opts.port, host: opts.host, noOpen: opts.open === false });
18744
+ });
18241
18745
  program.parseAsync(process.argv).catch((err) => {
18242
18746
  console.error(err);
18243
18747
  process.exit(1);