@harness-engineering/core 0.14.0 → 0.15.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/dist/index.js CHANGED
@@ -63,6 +63,7 @@ __export(index_exports, {
63
63
  DEFAULT_STATE: () => DEFAULT_STATE,
64
64
  DEFAULT_STREAM_INDEX: () => DEFAULT_STREAM_INDEX,
65
65
  DepDepthCollector: () => DepDepthCollector,
66
+ EXTENSION_MAP: () => EXTENSION_MAP,
66
67
  EmitInteractionInputSchema: () => EmitInteractionInputSchema,
67
68
  EntropyAnalyzer: () => EntropyAnalyzer,
68
69
  EntropyConfigSchema: () => EntropyConfigSchema,
@@ -97,6 +98,7 @@ __export(index_exports, {
97
98
  SharableForbiddenImportSchema: () => SharableForbiddenImportSchema,
98
99
  SharableLayerSchema: () => SharableLayerSchema,
99
100
  SharableSecurityRulesSchema: () => SharableSecurityRulesSchema,
101
+ SkillEventSchema: () => SkillEventSchema,
100
102
  StreamIndexSchema: () => StreamIndexSchema,
101
103
  StreamInfoSchema: () => StreamInfoSchema,
102
104
  ThresholdConfigSchema: () => ThresholdConfigSchema,
@@ -105,6 +107,7 @@ __export(index_exports, {
105
107
  VERSION: () => VERSION,
106
108
  ViolationSchema: () => ViolationSchema,
107
109
  addProvenance: () => addProvenance,
110
+ agentConfigRules: () => agentConfigRules,
108
111
  analyzeDiff: () => analyzeDiff,
109
112
  analyzeLearningPatterns: () => analyzeLearningPatterns,
110
113
  appendFailure: () => appendFailure,
@@ -112,6 +115,7 @@ __export(index_exports, {
112
115
  appendSessionEntry: () => appendSessionEntry,
113
116
  applyFixes: () => applyFixes,
114
117
  applyHotspotDowngrade: () => applyHotspotDowngrade,
118
+ applySyncChanges: () => applySyncChanges,
115
119
  archMatchers: () => archMatchers,
116
120
  archModule: () => archModule,
117
121
  architecture: () => architecture,
@@ -126,12 +130,14 @@ __export(index_exports, {
126
130
  checkEligibility: () => checkEligibility,
127
131
  checkEvidenceCoverage: () => checkEvidenceCoverage,
128
132
  classifyFinding: () => classifyFinding,
133
+ clearEventHashCache: () => clearEventHashCache,
129
134
  clearFailuresCache: () => clearFailuresCache,
130
135
  clearLearningsCache: () => clearLearningsCache,
131
136
  configureFeedback: () => configureFeedback,
132
137
  constraintRuleId: () => constraintRuleId,
133
138
  contextBudget: () => contextBudget,
134
139
  contextFilter: () => contextFilter,
140
+ countLearningEntries: () => countLearningEntries,
135
141
  createBoundaryValidator: () => createBoundaryValidator,
136
142
  createCommentedCodeFixes: () => createCommentedCodeFixes,
137
143
  createError: () => createError,
@@ -155,27 +161,34 @@ __export(index_exports, {
155
161
  detectCouplingViolations: () => detectCouplingViolations,
156
162
  detectDeadCode: () => detectDeadCode,
157
163
  detectDocDrift: () => detectDocDrift,
164
+ detectLanguage: () => detectLanguage,
158
165
  detectPatternViolations: () => detectPatternViolations,
159
166
  detectSizeBudgetViolations: () => detectSizeBudgetViolations,
160
167
  detectStack: () => detectStack,
161
168
  detectStaleConstraints: () => detectStaleConstraints,
162
169
  determineAssessment: () => determineAssessment,
163
170
  diff: () => diff,
171
+ emitEvent: () => emitEvent,
164
172
  executeWorkflow: () => executeWorkflow,
165
173
  expressRules: () => expressRules,
166
174
  extractBundle: () => extractBundle,
175
+ extractIndexEntry: () => extractIndexEntry,
167
176
  extractMarkdownLinks: () => extractMarkdownLinks,
168
177
  extractSections: () => extractSections,
169
178
  fanOutReview: () => fanOutReview,
179
+ formatEventTimeline: () => formatEventTimeline,
170
180
  formatFindingBlock: () => formatFindingBlock,
171
181
  formatGitHubComment: () => formatGitHubComment,
172
182
  formatGitHubSummary: () => formatGitHubSummary,
183
+ formatOutline: () => formatOutline,
173
184
  formatTerminalOutput: () => formatTerminalOutput,
174
185
  generateAgentsMap: () => generateAgentsMap,
175
186
  generateSuggestions: () => generateSuggestions,
176
187
  getActionEmitter: () => getActionEmitter,
177
188
  getExitCode: () => getExitCode,
178
189
  getFeedbackConfig: () => getFeedbackConfig,
190
+ getOutline: () => getOutline,
191
+ getParser: () => getParser,
179
192
  getPhaseCategories: () => getPhaseCategories,
180
193
  getStreamForBranch: () => getStreamForBranch,
181
194
  getUpdateNotification: () => getUpdateNotification,
@@ -186,24 +199,30 @@ __export(index_exports, {
186
199
  listActiveSessions: () => listActiveSessions,
187
200
  listStreams: () => listStreams,
188
201
  loadBudgetedLearnings: () => loadBudgetedLearnings,
202
+ loadEvents: () => loadEvents,
189
203
  loadFailures: () => loadFailures,
190
204
  loadHandoff: () => loadHandoff,
205
+ loadIndexEntries: () => loadIndexEntries,
191
206
  loadRelevantLearnings: () => loadRelevantLearnings,
192
207
  loadSessionSummary: () => loadSessionSummary,
193
208
  loadState: () => loadState,
194
209
  loadStreamIndex: () => loadStreamIndex,
195
210
  logAgentAction: () => logAgentAction,
211
+ mcpRules: () => mcpRules,
196
212
  migrateToStreams: () => migrateToStreams,
197
213
  networkRules: () => networkRules,
198
214
  nodeRules: () => nodeRules,
199
215
  parseDateFromEntry: () => parseDateFromEntry,
200
216
  parseDiff: () => parseDiff,
217
+ parseFile: () => parseFile,
218
+ parseFrontmatter: () => parseFrontmatter,
201
219
  parseManifest: () => parseManifest,
202
220
  parseRoadmap: () => parseRoadmap,
203
221
  parseSecurityConfig: () => parseSecurityConfig,
204
222
  parseSize: () => parseSize,
205
223
  pathTraversalRules: () => pathTraversalRules,
206
224
  previewFix: () => previewFix,
225
+ promoteSessionLearnings: () => promoteSessionLearnings,
207
226
  pruneLearnings: () => pruneLearnings,
208
227
  reactRules: () => reactRules,
209
228
  readCheckState: () => readCheckState,
@@ -215,6 +234,7 @@ __export(index_exports, {
215
234
  requestMultiplePeerReviews: () => requestMultiplePeerReviews,
216
235
  requestPeerReview: () => requestPeerReview,
217
236
  resetFeedbackConfig: () => resetFeedbackConfig,
237
+ resetParserCache: () => resetParserCache,
218
238
  resolveFileToLayer: () => resolveFileToLayer,
219
239
  resolveModelTier: () => resolveModelTier,
220
240
  resolveRuleSeverity: () => resolveRuleSeverity,
@@ -236,6 +256,7 @@ __export(index_exports, {
236
256
  saveState: () => saveState,
237
257
  saveStreamIndex: () => saveStreamIndex,
238
258
  scopeContext: () => scopeContext,
259
+ searchSymbols: () => searchSymbols,
239
260
  secretRules: () => secretRules,
240
261
  serializeRoadmap: () => serializeRoadmap,
241
262
  setActiveStream: () => setActiveStream,
@@ -246,6 +267,8 @@ __export(index_exports, {
246
267
  tagUncitedFindings: () => tagUncitedFindings,
247
268
  touchStream: () => touchStream,
248
269
  trackAction: () => trackAction,
270
+ unfoldRange: () => unfoldRange,
271
+ unfoldSymbol: () => unfoldSymbol,
249
272
  updateSessionEntryStatus: () => updateSessionEntryStatus,
250
273
  updateSessionIndex: () => updateSessionIndex,
251
274
  validateAgentsMap: () => validateAgentsMap,
@@ -284,17 +307,17 @@ var import_node_path = require("path");
284
307
  var import_glob = require("glob");
285
308
  var accessAsync = (0, import_util.promisify)(import_fs.access);
286
309
  var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
287
- async function fileExists(path22) {
310
+ async function fileExists(path23) {
288
311
  try {
289
- await accessAsync(path22, import_fs.constants.F_OK);
312
+ await accessAsync(path23, import_fs.constants.F_OK);
290
313
  return true;
291
314
  } catch {
292
315
  return false;
293
316
  }
294
317
  }
295
- async function readFileContent(path22) {
318
+ async function readFileContent(path23) {
296
319
  try {
297
- const content = await readFileAsync(path22, "utf-8");
320
+ const content = await readFileAsync(path23, "utf-8");
298
321
  return (0, import_types.Ok)(content);
299
322
  } catch (error) {
300
323
  return (0, import_types.Err)(error);
@@ -345,15 +368,15 @@ function validateConfig(data, schema) {
345
368
  let message = "Configuration validation failed";
346
369
  const suggestions = [];
347
370
  if (firstError) {
348
- const path22 = firstError.path.join(".");
349
- const pathDisplay = path22 ? ` at "${path22}"` : "";
371
+ const path23 = firstError.path.join(".");
372
+ const pathDisplay = path23 ? ` at "${path23}"` : "";
350
373
  if (firstError.code === "invalid_type") {
351
374
  const received = firstError.received;
352
375
  const expected = firstError.expected;
353
376
  if (received === "undefined") {
354
377
  code = "MISSING_FIELD";
355
378
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
356
- suggestions.push(`Field "${path22}" is required and must be of type "${expected}"`);
379
+ suggestions.push(`Field "${path23}" is required and must be of type "${expected}"`);
357
380
  } else {
358
381
  code = "INVALID_TYPE";
359
382
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -569,27 +592,27 @@ function extractSections(content) {
569
592
  }
570
593
  return sections.map((section) => buildAgentMapSection(section, lines));
571
594
  }
572
- function isExternalLink(path22) {
573
- return path22.startsWith("http://") || path22.startsWith("https://") || path22.startsWith("#") || path22.startsWith("mailto:");
595
+ function isExternalLink(path23) {
596
+ return path23.startsWith("http://") || path23.startsWith("https://") || path23.startsWith("#") || path23.startsWith("mailto:");
574
597
  }
575
598
  function resolveLinkPath(linkPath, baseDir) {
576
599
  return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
577
600
  }
578
- async function validateAgentsMap(path22 = "./AGENTS.md") {
579
- const contentResult = await readFileContent(path22);
601
+ async function validateAgentsMap(path23 = "./AGENTS.md") {
602
+ const contentResult = await readFileContent(path23);
580
603
  if (!contentResult.ok) {
581
604
  return (0, import_types.Err)(
582
605
  createError(
583
606
  "PARSE_ERROR",
584
607
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
585
- { path: path22 },
608
+ { path: path23 },
586
609
  ["Ensure the file exists", "Check file permissions"]
587
610
  )
588
611
  );
589
612
  }
590
613
  const content = contentResult.value;
591
614
  const sections = extractSections(content);
592
- const baseDir = (0, import_path.dirname)(path22);
615
+ const baseDir = (0, import_path.dirname)(path23);
593
616
  const sectionTitles = sections.map((s) => s.title);
594
617
  const missingSections = REQUIRED_SECTIONS.filter(
595
618
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -730,8 +753,8 @@ async function checkDocCoverage(domain, options = {}) {
730
753
 
731
754
  // src/context/knowledge-map.ts
732
755
  var import_path3 = require("path");
733
- function suggestFix(path22, existingFiles) {
734
- const targetName = (0, import_path3.basename)(path22).toLowerCase();
756
+ function suggestFix(path23, existingFiles) {
757
+ const targetName = (0, import_path3.basename)(path23).toLowerCase();
735
758
  const similar = existingFiles.find((file) => {
736
759
  const fileName = (0, import_path3.basename)(file).toLowerCase();
737
760
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -739,7 +762,7 @@ function suggestFix(path22, existingFiles) {
739
762
  if (similar) {
740
763
  return `Did you mean "${similar}"?`;
741
764
  }
742
- return `Create the file "${path22}" or remove the link`;
765
+ return `Create the file "${path23}" or remove the link`;
743
766
  }
744
767
  async function validateKnowledgeMap(rootDir = process.cwd()) {
745
768
  const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
@@ -1345,8 +1368,8 @@ function createBoundaryValidator(schema, name) {
1345
1368
  return (0, import_types.Ok)(result.data);
1346
1369
  }
1347
1370
  const suggestions = result.error.issues.map((issue) => {
1348
- const path22 = issue.path.join(".");
1349
- return path22 ? `${path22}: ${issue.message}` : issue.message;
1371
+ const path23 = issue.path.join(".");
1372
+ return path23 ? `${path23}: ${issue.message}` : issue.message;
1350
1373
  });
1351
1374
  return (0, import_types.Err)(
1352
1375
  createError(
@@ -1978,11 +2001,11 @@ function processExportListSpecifiers(exportDecl, exports2) {
1978
2001
  var TypeScriptParser = class {
1979
2002
  name = "typescript";
1980
2003
  extensions = [".ts", ".tsx", ".mts", ".cts"];
1981
- async parseFile(path22) {
1982
- const contentResult = await readFileContent(path22);
2004
+ async parseFile(path23) {
2005
+ const contentResult = await readFileContent(path23);
1983
2006
  if (!contentResult.ok) {
1984
2007
  return (0, import_types.Err)(
1985
- createParseError("NOT_FOUND", `File not found: ${path22}`, { path: path22 }, [
2008
+ createParseError("NOT_FOUND", `File not found: ${path23}`, { path: path23 }, [
1986
2009
  "Check that the file exists",
1987
2010
  "Verify the path is correct"
1988
2011
  ])
@@ -1992,7 +2015,7 @@ var TypeScriptParser = class {
1992
2015
  const ast = (0, import_typescript_estree.parse)(contentResult.value, {
1993
2016
  loc: true,
1994
2017
  range: true,
1995
- jsx: path22.endsWith(".tsx"),
2018
+ jsx: path23.endsWith(".tsx"),
1996
2019
  errorOnUnknownASTType: false
1997
2020
  });
1998
2021
  return (0, import_types.Ok)({
@@ -2003,7 +2026,7 @@ var TypeScriptParser = class {
2003
2026
  } catch (e) {
2004
2027
  const error = e;
2005
2028
  return (0, import_types.Err)(
2006
- createParseError("SYNTAX_ERROR", `Failed to parse ${path22}: ${error.message}`, { path: path22 }, [
2029
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path23}: ${error.message}`, { path: path23 }, [
2007
2030
  "Check for syntax errors in the file",
2008
2031
  "Ensure valid TypeScript syntax"
2009
2032
  ])
@@ -2188,22 +2211,22 @@ function extractInlineRefs(content) {
2188
2211
  }
2189
2212
  return refs;
2190
2213
  }
2191
- async function parseDocumentationFile(path22) {
2192
- const contentResult = await readFileContent(path22);
2214
+ async function parseDocumentationFile(path23) {
2215
+ const contentResult = await readFileContent(path23);
2193
2216
  if (!contentResult.ok) {
2194
2217
  return (0, import_types.Err)(
2195
2218
  createEntropyError(
2196
2219
  "PARSE_ERROR",
2197
- `Failed to read documentation file: ${path22}`,
2198
- { file: path22 },
2220
+ `Failed to read documentation file: ${path23}`,
2221
+ { file: path23 },
2199
2222
  ["Check that the file exists"]
2200
2223
  )
2201
2224
  );
2202
2225
  }
2203
2226
  const content = contentResult.value;
2204
- const type = path22.endsWith(".md") ? "markdown" : "text";
2227
+ const type = path23.endsWith(".md") ? "markdown" : "text";
2205
2228
  return (0, import_types.Ok)({
2206
- path: path22,
2229
+ path: path23,
2207
2230
  type,
2208
2231
  content,
2209
2232
  codeBlocks: extractCodeBlocks(content),
@@ -6868,6 +6891,8 @@ var SESSION_INDEX_FILE = "index.md";
6868
6891
  var SUMMARY_FILE = "summary.md";
6869
6892
  var SESSION_STATE_FILE = "session-state.json";
6870
6893
  var ARCHIVE_DIR = "archive";
6894
+ var CONTENT_HASHES_FILE = "content-hashes.json";
6895
+ var EVENTS_FILE = "events.jsonl";
6871
6896
 
6872
6897
  // src/state/stream-resolver.ts
6873
6898
  var STREAMS_DIR = "streams";
@@ -7210,6 +7235,85 @@ async function saveState(projectPath, state, stream, session) {
7210
7235
  // src/state/learnings.ts
7211
7236
  var fs9 = __toESM(require("fs"));
7212
7237
  var path6 = __toESM(require("path"));
7238
+ var crypto = __toESM(require("crypto"));
7239
+ function parseFrontmatter(line) {
7240
+ const match = line.match(/^<!--\s+hash:([a-f0-9]+)(?:\s+tags:([^\s]+))?\s+-->/);
7241
+ if (!match) return null;
7242
+ const hash = match[1];
7243
+ const tags = match[2] ? match[2].split(",").filter(Boolean) : [];
7244
+ return { hash, tags };
7245
+ }
7246
+ function computeEntryHash(text) {
7247
+ return crypto.createHash("sha256").update(text).digest("hex").slice(0, 8);
7248
+ }
7249
+ function normalizeLearningContent(text) {
7250
+ let normalized = text;
7251
+ normalized = normalized.replace(/\d{4}-\d{2}-\d{2}/g, "");
7252
+ normalized = normalized.replace(/\[skill:[^\]]*\]/g, "");
7253
+ normalized = normalized.replace(/\[outcome:[^\]]*\]/g, "");
7254
+ normalized = normalized.replace(/^[\s]*[-*]\s+/gm, "");
7255
+ normalized = normalized.replace(/\*\*/g, "");
7256
+ normalized = normalized.replace(/:\s*/g, " ");
7257
+ normalized = normalized.toLowerCase();
7258
+ normalized = normalized.replace(/\s+/g, " ").trim();
7259
+ return normalized;
7260
+ }
7261
+ function computeContentHash(text) {
7262
+ return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
7263
+ }
7264
+ function loadContentHashes(stateDir) {
7265
+ const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
7266
+ if (!fs9.existsSync(hashesPath)) return {};
7267
+ try {
7268
+ const raw = fs9.readFileSync(hashesPath, "utf-8");
7269
+ const parsed = JSON.parse(raw);
7270
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
7271
+ return parsed;
7272
+ } catch {
7273
+ return {};
7274
+ }
7275
+ }
7276
+ function saveContentHashes(stateDir, index) {
7277
+ const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
7278
+ fs9.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
7279
+ }
7280
+ function rebuildContentHashes(stateDir) {
7281
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7282
+ if (!fs9.existsSync(learningsPath)) return {};
7283
+ const content = fs9.readFileSync(learningsPath, "utf-8");
7284
+ const lines = content.split("\n");
7285
+ const index = {};
7286
+ for (let i = 0; i < lines.length; i++) {
7287
+ const line = lines[i];
7288
+ const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
7289
+ if (isDatedBullet) {
7290
+ const learningMatch = line.match(/:\*\*\s*(.+)$/);
7291
+ if (learningMatch?.[1]) {
7292
+ const normalized = normalizeLearningContent(learningMatch[1]);
7293
+ const hash = computeContentHash(normalized);
7294
+ const dateMatch = line.match(/(\d{4}-\d{2}-\d{2})/);
7295
+ index[hash] = { date: dateMatch?.[1] ?? "", line: i + 1 };
7296
+ }
7297
+ }
7298
+ }
7299
+ saveContentHashes(stateDir, index);
7300
+ return index;
7301
+ }
7302
+ function extractIndexEntry(entry) {
7303
+ const lines = entry.split("\n");
7304
+ const summary = lines[0] ?? entry;
7305
+ const tags = [];
7306
+ const skillMatch = entry.match(/\[skill:([^\]]+)\]/);
7307
+ if (skillMatch?.[1]) tags.push(skillMatch[1]);
7308
+ const outcomeMatch = entry.match(/\[outcome:([^\]]+)\]/);
7309
+ if (outcomeMatch?.[1]) tags.push(outcomeMatch[1]);
7310
+ return {
7311
+ hash: computeEntryHash(entry),
7312
+ tags,
7313
+ summary,
7314
+ fullText: entry
7315
+ };
7316
+ }
7213
7317
  var learningsCacheMap = /* @__PURE__ */ new Map();
7214
7318
  function clearLearningsCache() {
7215
7319
  learningsCacheMap.clear();
@@ -7221,27 +7325,55 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
7221
7325
  const stateDir = dirResult.value;
7222
7326
  const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7223
7327
  fs9.mkdirSync(stateDir, { recursive: true });
7328
+ const normalizedContent = normalizeLearningContent(learning);
7329
+ const contentHash = computeContentHash(normalizedContent);
7330
+ const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
7331
+ let contentHashes;
7332
+ if (fs9.existsSync(hashesPath)) {
7333
+ contentHashes = loadContentHashes(stateDir);
7334
+ if (Object.keys(contentHashes).length === 0 && fs9.existsSync(learningsPath)) {
7335
+ contentHashes = rebuildContentHashes(stateDir);
7336
+ }
7337
+ } else if (fs9.existsSync(learningsPath)) {
7338
+ contentHashes = rebuildContentHashes(stateDir);
7339
+ } else {
7340
+ contentHashes = {};
7341
+ }
7342
+ if (contentHashes[contentHash]) {
7343
+ return (0, import_types.Ok)(void 0);
7344
+ }
7224
7345
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7225
- let entry;
7346
+ const fmTags = [];
7347
+ if (skillName) fmTags.push(skillName);
7348
+ if (outcome) fmTags.push(outcome);
7349
+ let bulletLine;
7226
7350
  if (skillName && outcome) {
7227
- entry = `
7228
- - **${timestamp} [skill:${skillName}] [outcome:${outcome}]:** ${learning}
7229
- `;
7351
+ bulletLine = `- **${timestamp} [skill:${skillName}] [outcome:${outcome}]:** ${learning}`;
7230
7352
  } else if (skillName) {
7231
- entry = `
7232
- - **${timestamp} [skill:${skillName}]:** ${learning}
7233
- `;
7353
+ bulletLine = `- **${timestamp} [skill:${skillName}]:** ${learning}`;
7234
7354
  } else {
7235
- entry = `
7236
- - **${timestamp}:** ${learning}
7237
- `;
7355
+ bulletLine = `- **${timestamp}:** ${learning}`;
7238
7356
  }
7357
+ const hash = crypto.createHash("sha256").update(bulletLine).digest("hex").slice(0, 8);
7358
+ const tagsStr = fmTags.length > 0 ? ` tags:${fmTags.join(",")}` : "";
7359
+ const frontmatter = `<!-- hash:${hash}${tagsStr} -->`;
7360
+ const entry = `
7361
+ ${frontmatter}
7362
+ ${bulletLine}
7363
+ `;
7364
+ let existingLineCount;
7239
7365
  if (!fs9.existsSync(learningsPath)) {
7240
7366
  fs9.writeFileSync(learningsPath, `# Learnings
7241
7367
  ${entry}`);
7368
+ existingLineCount = 1;
7242
7369
  } else {
7370
+ const existingContent = fs9.readFileSync(learningsPath, "utf-8");
7371
+ existingLineCount = existingContent.split("\n").length;
7243
7372
  fs9.appendFileSync(learningsPath, entry);
7244
7373
  }
7374
+ const bulletLine_lineNum = existingLineCount + 2;
7375
+ contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
7376
+ saveContentHashes(stateDir, contentHashes);
7245
7377
  learningsCacheMap.delete(learningsPath);
7246
7378
  return (0, import_types.Ok)(void 0);
7247
7379
  } catch (error) {
@@ -7289,7 +7421,30 @@ function analyzeLearningPatterns(entries) {
7289
7421
  return patterns.sort((a, b) => b.count - a.count);
7290
7422
  }
7291
7423
  async function loadBudgetedLearnings(projectPath, options) {
7292
- const { intent, tokenBudget = 1e3, skill, session, stream } = options;
7424
+ const { intent, tokenBudget = 1e3, skill, session, stream, depth = "summary" } = options;
7425
+ if (depth === "index") {
7426
+ const indexEntries = [];
7427
+ if (session) {
7428
+ const sessionResult = await loadIndexEntries(projectPath, skill, stream, session);
7429
+ if (sessionResult.ok) indexEntries.push(...sessionResult.value);
7430
+ }
7431
+ const globalResult2 = await loadIndexEntries(projectPath, skill, stream);
7432
+ if (globalResult2.ok) {
7433
+ const sessionHashes = new Set(indexEntries.map((e) => e.hash));
7434
+ const uniqueGlobal = globalResult2.value.filter((e) => !sessionHashes.has(e.hash));
7435
+ indexEntries.push(...uniqueGlobal);
7436
+ }
7437
+ const budgeted2 = [];
7438
+ let totalTokens2 = 0;
7439
+ for (const entry of indexEntries) {
7440
+ const separator = budgeted2.length > 0 ? "\n" : "";
7441
+ const entryCost = estimateTokens(entry.summary + separator);
7442
+ if (totalTokens2 + entryCost > tokenBudget) break;
7443
+ budgeted2.push(entry.summary);
7444
+ totalTokens2 += entryCost;
7445
+ }
7446
+ return (0, import_types.Ok)(budgeted2);
7447
+ }
7293
7448
  const sortByRecencyAndRelevance = (entries) => {
7294
7449
  return [...entries].sort((a, b) => {
7295
7450
  const dateA = parseDateFromEntry(a) ?? "0000-00-00";
@@ -7308,7 +7463,9 @@ async function loadBudgetedLearnings(projectPath, options) {
7308
7463
  }
7309
7464
  const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
7310
7465
  if (globalResult.ok) {
7311
- allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
7466
+ const sessionSet = new Set(allEntries.map((e) => e.trim()));
7467
+ const uniqueGlobal = globalResult.value.filter((e) => !sessionSet.has(e.trim()));
7468
+ allEntries.push(...sortByRecencyAndRelevance(uniqueGlobal));
7312
7469
  }
7313
7470
  const budgeted = [];
7314
7471
  let totalTokens = 0;
@@ -7321,6 +7478,68 @@ async function loadBudgetedLearnings(projectPath, options) {
7321
7478
  }
7322
7479
  return (0, import_types.Ok)(budgeted);
7323
7480
  }
7481
+ async function loadIndexEntries(projectPath, skillName, stream, session) {
7482
+ try {
7483
+ const dirResult = await getStateDir(projectPath, stream, session);
7484
+ if (!dirResult.ok) return dirResult;
7485
+ const stateDir = dirResult.value;
7486
+ const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
7487
+ if (!fs9.existsSync(learningsPath)) {
7488
+ return (0, import_types.Ok)([]);
7489
+ }
7490
+ const content = fs9.readFileSync(learningsPath, "utf-8");
7491
+ const lines = content.split("\n");
7492
+ const indexEntries = [];
7493
+ let pendingFrontmatter = null;
7494
+ let currentBlock = [];
7495
+ for (const line of lines) {
7496
+ if (line.startsWith("# ")) continue;
7497
+ const fm = parseFrontmatter(line);
7498
+ if (fm) {
7499
+ pendingFrontmatter = fm;
7500
+ continue;
7501
+ }
7502
+ const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
7503
+ const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
7504
+ if (isDatedBullet || isHeading) {
7505
+ if (pendingFrontmatter) {
7506
+ indexEntries.push({
7507
+ hash: pendingFrontmatter.hash,
7508
+ tags: pendingFrontmatter.tags,
7509
+ summary: line,
7510
+ fullText: ""
7511
+ // Placeholder — full text not loaded in index mode
7512
+ });
7513
+ pendingFrontmatter = null;
7514
+ } else {
7515
+ const idx = extractIndexEntry(line);
7516
+ indexEntries.push({
7517
+ hash: idx.hash,
7518
+ tags: idx.tags,
7519
+ summary: line,
7520
+ fullText: ""
7521
+ });
7522
+ }
7523
+ currentBlock = [line];
7524
+ } else if (line.trim() !== "" && currentBlock.length > 0) {
7525
+ currentBlock.push(line);
7526
+ }
7527
+ }
7528
+ if (skillName) {
7529
+ const filtered = indexEntries.filter(
7530
+ (e) => e.tags.includes(skillName) || e.summary.includes(`[skill:${skillName}]`)
7531
+ );
7532
+ return (0, import_types.Ok)(filtered);
7533
+ }
7534
+ return (0, import_types.Ok)(indexEntries);
7535
+ } catch (error) {
7536
+ return (0, import_types.Err)(
7537
+ new Error(
7538
+ `Failed to load index entries: ${error instanceof Error ? error.message : String(error)}`
7539
+ )
7540
+ );
7541
+ }
7542
+ }
7324
7543
  async function loadRelevantLearnings(projectPath, skillName, stream, session) {
7325
7544
  try {
7326
7545
  const dirResult = await getStateDir(projectPath, stream, session);
@@ -7343,6 +7562,7 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
7343
7562
  let currentBlock = [];
7344
7563
  for (const line of lines) {
7345
7564
  if (line.startsWith("# ")) continue;
7565
+ if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
7346
7566
  const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
7347
7567
  const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
7348
7568
  if (isDatedBullet || isHeading) {
@@ -7452,6 +7672,68 @@ async function pruneLearnings(projectPath, stream) {
7452
7672
  );
7453
7673
  }
7454
7674
  }
7675
+ var PROMOTABLE_OUTCOMES = ["gotcha", "decision", "observation"];
7676
+ function isGeneralizable(entry) {
7677
+ for (const outcome of PROMOTABLE_OUTCOMES) {
7678
+ if (entry.includes(`[outcome:${outcome}]`)) return true;
7679
+ }
7680
+ return false;
7681
+ }
7682
+ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
7683
+ try {
7684
+ const sessionResult = await loadRelevantLearnings(projectPath, void 0, stream, sessionSlug);
7685
+ if (!sessionResult.ok) return sessionResult;
7686
+ const sessionEntries = sessionResult.value;
7687
+ if (sessionEntries.length === 0) {
7688
+ return (0, import_types.Ok)({ promoted: 0, skipped: 0 });
7689
+ }
7690
+ const toPromote = [];
7691
+ let skipped = 0;
7692
+ for (const entry of sessionEntries) {
7693
+ if (isGeneralizable(entry)) {
7694
+ toPromote.push(entry);
7695
+ } else {
7696
+ skipped++;
7697
+ }
7698
+ }
7699
+ if (toPromote.length === 0) {
7700
+ return (0, import_types.Ok)({ promoted: 0, skipped });
7701
+ }
7702
+ const dirResult = await getStateDir(projectPath, stream);
7703
+ if (!dirResult.ok) return dirResult;
7704
+ const stateDir = dirResult.value;
7705
+ const globalPath = path6.join(stateDir, LEARNINGS_FILE);
7706
+ const existingGlobal = fs9.existsSync(globalPath) ? fs9.readFileSync(globalPath, "utf-8") : "";
7707
+ const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
7708
+ if (newEntries.length === 0) {
7709
+ return (0, import_types.Ok)({ promoted: 0, skipped: skipped + toPromote.length });
7710
+ }
7711
+ const promotedContent = newEntries.join("\n\n") + "\n";
7712
+ if (!existingGlobal) {
7713
+ fs9.writeFileSync(globalPath, `# Learnings
7714
+
7715
+ ${promotedContent}`);
7716
+ } else {
7717
+ fs9.appendFileSync(globalPath, "\n\n" + promotedContent);
7718
+ }
7719
+ learningsCacheMap.delete(globalPath);
7720
+ return (0, import_types.Ok)({
7721
+ promoted: newEntries.length,
7722
+ skipped: skipped + (toPromote.length - newEntries.length)
7723
+ });
7724
+ } catch (error) {
7725
+ return (0, import_types.Err)(
7726
+ new Error(
7727
+ `Failed to promote session learnings: ${error instanceof Error ? error.message : String(error)}`
7728
+ )
7729
+ );
7730
+ }
7731
+ }
7732
+ async function countLearningEntries(projectPath, stream) {
7733
+ const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
7734
+ if (!loadResult.ok) return 0;
7735
+ return loadResult.value.length;
7736
+ }
7455
7737
 
7456
7738
  // src/state/failures.ts
7457
7739
  var fs10 = __toESM(require("fs"));
@@ -7913,6 +8195,151 @@ async function archiveSession(projectPath, sessionSlug) {
7913
8195
  }
7914
8196
  }
7915
8197
 
8198
+ // src/state/events.ts
8199
+ var fs16 = __toESM(require("fs"));
8200
+ var path13 = __toESM(require("path"));
8201
+ var import_zod6 = require("zod");
8202
+ var SkillEventSchema = import_zod6.z.object({
8203
+ timestamp: import_zod6.z.string(),
8204
+ skill: import_zod6.z.string(),
8205
+ session: import_zod6.z.string().optional(),
8206
+ type: import_zod6.z.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
8207
+ summary: import_zod6.z.string(),
8208
+ data: import_zod6.z.record(import_zod6.z.unknown()).optional(),
8209
+ refs: import_zod6.z.array(import_zod6.z.string()).optional(),
8210
+ contentHash: import_zod6.z.string().optional()
8211
+ });
8212
+ function computeEventHash(event, session) {
8213
+ const identity = `${event.skill}|${event.type}|${event.summary}|${session ?? ""}`;
8214
+ return computeContentHash(identity);
8215
+ }
8216
+ var knownHashesCache = /* @__PURE__ */ new Map();
8217
+ function loadKnownHashes(eventsPath) {
8218
+ const cached = knownHashesCache.get(eventsPath);
8219
+ if (cached) return cached;
8220
+ const hashes = /* @__PURE__ */ new Set();
8221
+ if (fs16.existsSync(eventsPath)) {
8222
+ const content = fs16.readFileSync(eventsPath, "utf-8");
8223
+ const lines = content.split("\n").filter((line) => line.trim() !== "");
8224
+ for (const line of lines) {
8225
+ try {
8226
+ const existing = JSON.parse(line);
8227
+ if (existing.contentHash) {
8228
+ hashes.add(existing.contentHash);
8229
+ }
8230
+ } catch {
8231
+ }
8232
+ }
8233
+ }
8234
+ knownHashesCache.set(eventsPath, hashes);
8235
+ return hashes;
8236
+ }
8237
+ function clearEventHashCache() {
8238
+ knownHashesCache.clear();
8239
+ }
8240
+ async function emitEvent(projectPath, event, options) {
8241
+ try {
8242
+ const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
8243
+ if (!dirResult.ok) return dirResult;
8244
+ const stateDir = dirResult.value;
8245
+ const eventsPath = path13.join(stateDir, EVENTS_FILE);
8246
+ fs16.mkdirSync(stateDir, { recursive: true });
8247
+ const contentHash = computeEventHash(event, options?.session);
8248
+ const knownHashes = loadKnownHashes(eventsPath);
8249
+ if (knownHashes.has(contentHash)) {
8250
+ return (0, import_types.Ok)({ written: false, reason: "duplicate" });
8251
+ }
8252
+ const fullEvent = {
8253
+ ...event,
8254
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8255
+ contentHash
8256
+ };
8257
+ if (options?.session) {
8258
+ fullEvent.session = options.session;
8259
+ }
8260
+ fs16.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
8261
+ knownHashes.add(contentHash);
8262
+ return (0, import_types.Ok)({ written: true });
8263
+ } catch (error) {
8264
+ return (0, import_types.Err)(
8265
+ new Error(`Failed to emit event: ${error instanceof Error ? error.message : String(error)}`)
8266
+ );
8267
+ }
8268
+ }
8269
+ async function loadEvents(projectPath, options) {
8270
+ try {
8271
+ const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
8272
+ if (!dirResult.ok) return dirResult;
8273
+ const stateDir = dirResult.value;
8274
+ const eventsPath = path13.join(stateDir, EVENTS_FILE);
8275
+ if (!fs16.existsSync(eventsPath)) {
8276
+ return (0, import_types.Ok)([]);
8277
+ }
8278
+ const content = fs16.readFileSync(eventsPath, "utf-8");
8279
+ const lines = content.split("\n").filter((line) => line.trim() !== "");
8280
+ const events = [];
8281
+ for (const line of lines) {
8282
+ try {
8283
+ const parsed = JSON.parse(line);
8284
+ const result = SkillEventSchema.safeParse(parsed);
8285
+ if (result.success) {
8286
+ events.push(result.data);
8287
+ }
8288
+ } catch {
8289
+ }
8290
+ }
8291
+ return (0, import_types.Ok)(events);
8292
+ } catch (error) {
8293
+ return (0, import_types.Err)(
8294
+ new Error(`Failed to load events: ${error instanceof Error ? error.message : String(error)}`)
8295
+ );
8296
+ }
8297
+ }
8298
+ function formatPhaseTransition(event) {
8299
+ const data = event.data;
8300
+ const suffix = data?.taskCount ? ` (${data.taskCount} tasks)` : "";
8301
+ return `phase: ${data?.from ?? "?"} -> ${data?.to ?? "?"}${suffix}`;
8302
+ }
8303
+ function formatGateResult(event) {
8304
+ const data = event.data;
8305
+ const status = data?.passed ? "passed" : "failed";
8306
+ const checks = data?.checks?.map((c) => `${c.name} ${c.passed ? "Y" : "N"}`).join(", ");
8307
+ return checks ? `gate: ${status} (${checks})` : `gate: ${status}`;
8308
+ }
8309
+ function formatHandoffDetail(event) {
8310
+ const data = event.data;
8311
+ const direction = data?.toSkill ? ` -> ${data.toSkill}` : "";
8312
+ return `handoff: ${event.summary}${direction}`;
8313
+ }
8314
+ var EVENT_FORMATTERS = {
8315
+ phase_transition: formatPhaseTransition,
8316
+ gate_result: formatGateResult,
8317
+ decision: (event) => `decision: ${event.summary}`,
8318
+ handoff: formatHandoffDetail,
8319
+ error: (event) => `error: ${event.summary}`,
8320
+ checkpoint: (event) => `checkpoint: ${event.summary}`
8321
+ };
8322
+ function formatEventTimeline(events, limit = 20) {
8323
+ if (events.length === 0) return "";
8324
+ const recent = events.slice(-limit);
8325
+ return recent.map((event) => {
8326
+ const time = formatTime(event.timestamp);
8327
+ const formatter = EVENT_FORMATTERS[event.type];
8328
+ const detail = formatter ? formatter(event) : event.summary;
8329
+ return `- ${time} [${event.skill}] ${detail}`;
8330
+ }).join("\n");
8331
+ }
8332
+ function formatTime(timestamp) {
8333
+ try {
8334
+ const date = new Date(timestamp);
8335
+ const hours = String(date.getHours()).padStart(2, "0");
8336
+ const minutes = String(date.getMinutes()).padStart(2, "0");
8337
+ return `${hours}:${minutes}`;
8338
+ } catch {
8339
+ return "??:??";
8340
+ }
8341
+ }
8342
+
7916
8343
  // src/workflow/runner.ts
7917
8344
  async function executeWorkflow(workflow, executor) {
7918
8345
  const stepResults = [];
@@ -8062,7 +8489,8 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
8062
8489
  }
8063
8490
 
8064
8491
  // src/security/scanner.ts
8065
- var fs17 = __toESM(require("fs/promises"));
8492
+ var fs18 = __toESM(require("fs/promises"));
8493
+ var import_minimatch5 = require("minimatch");
8066
8494
 
8067
8495
  // src/security/rules/registry.ts
8068
8496
  var RuleRegistry = class {
@@ -8093,7 +8521,7 @@ var RuleRegistry = class {
8093
8521
  };
8094
8522
 
8095
8523
  // src/security/config.ts
8096
- var import_zod6 = require("zod");
8524
+ var import_zod7 = require("zod");
8097
8525
 
8098
8526
  // src/security/types.ts
8099
8527
  var DEFAULT_SECURITY_CONFIG = {
@@ -8104,19 +8532,19 @@ var DEFAULT_SECURITY_CONFIG = {
8104
8532
  };
8105
8533
 
8106
8534
  // src/security/config.ts
8107
- var RuleOverrideSchema = import_zod6.z.enum(["off", "error", "warning", "info"]);
8108
- var SecurityConfigSchema = import_zod6.z.object({
8109
- enabled: import_zod6.z.boolean().default(true),
8110
- strict: import_zod6.z.boolean().default(false),
8111
- rules: import_zod6.z.record(import_zod6.z.string(), RuleOverrideSchema).optional().default({}),
8112
- exclude: import_zod6.z.array(import_zod6.z.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
8113
- external: import_zod6.z.object({
8114
- semgrep: import_zod6.z.object({
8115
- enabled: import_zod6.z.union([import_zod6.z.literal("auto"), import_zod6.z.boolean()]).default("auto"),
8116
- rulesets: import_zod6.z.array(import_zod6.z.string()).optional()
8535
+ var RuleOverrideSchema = import_zod7.z.enum(["off", "error", "warning", "info"]);
8536
+ var SecurityConfigSchema = import_zod7.z.object({
8537
+ enabled: import_zod7.z.boolean().default(true),
8538
+ strict: import_zod7.z.boolean().default(false),
8539
+ rules: import_zod7.z.record(import_zod7.z.string(), RuleOverrideSchema).optional().default({}),
8540
+ exclude: import_zod7.z.array(import_zod7.z.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
8541
+ external: import_zod7.z.object({
8542
+ semgrep: import_zod7.z.object({
8543
+ enabled: import_zod7.z.union([import_zod7.z.literal("auto"), import_zod7.z.boolean()]).default("auto"),
8544
+ rulesets: import_zod7.z.array(import_zod7.z.string()).optional()
8117
8545
  }).optional(),
8118
- gitleaks: import_zod6.z.object({
8119
- enabled: import_zod6.z.union([import_zod6.z.literal("auto"), import_zod6.z.boolean()]).default("auto")
8546
+ gitleaks: import_zod7.z.object({
8547
+ enabled: import_zod7.z.union([import_zod7.z.literal("auto"), import_zod7.z.boolean()]).default("auto")
8120
8548
  }).optional()
8121
8549
  }).optional()
8122
8550
  });
@@ -8149,15 +8577,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
8149
8577
  }
8150
8578
 
8151
8579
  // src/security/stack-detector.ts
8152
- var fs16 = __toESM(require("fs"));
8153
- var path13 = __toESM(require("path"));
8580
+ var fs17 = __toESM(require("fs"));
8581
+ var path14 = __toESM(require("path"));
8154
8582
  function detectStack(projectRoot) {
8155
8583
  const stacks = [];
8156
- const pkgJsonPath = path13.join(projectRoot, "package.json");
8157
- if (fs16.existsSync(pkgJsonPath)) {
8584
+ const pkgJsonPath = path14.join(projectRoot, "package.json");
8585
+ if (fs17.existsSync(pkgJsonPath)) {
8158
8586
  stacks.push("node");
8159
8587
  try {
8160
- const pkgJson = JSON.parse(fs16.readFileSync(pkgJsonPath, "utf-8"));
8588
+ const pkgJson = JSON.parse(fs17.readFileSync(pkgJsonPath, "utf-8"));
8161
8589
  const allDeps = {
8162
8590
  ...pkgJson.dependencies,
8163
8591
  ...pkgJson.devDependencies
@@ -8172,13 +8600,13 @@ function detectStack(projectRoot) {
8172
8600
  } catch {
8173
8601
  }
8174
8602
  }
8175
- const goModPath = path13.join(projectRoot, "go.mod");
8176
- if (fs16.existsSync(goModPath)) {
8603
+ const goModPath = path14.join(projectRoot, "go.mod");
8604
+ if (fs17.existsSync(goModPath)) {
8177
8605
  stacks.push("go");
8178
8606
  }
8179
- const requirementsPath = path13.join(projectRoot, "requirements.txt");
8180
- const pyprojectPath = path13.join(projectRoot, "pyproject.toml");
8181
- if (fs16.existsSync(requirementsPath) || fs16.existsSync(pyprojectPath)) {
8607
+ const requirementsPath = path14.join(projectRoot, "requirements.txt");
8608
+ const pyprojectPath = path14.join(projectRoot, "pyproject.toml");
8609
+ if (fs17.existsSync(requirementsPath) || fs17.existsSync(pyprojectPath)) {
8182
8610
  stacks.push("python");
8183
8611
  }
8184
8612
  return stacks;
@@ -8242,6 +8670,72 @@ var secretRules = [
8242
8670
  message: "Hardcoded JWT token detected",
8243
8671
  remediation: "Tokens should be fetched at runtime, not embedded in source",
8244
8672
  references: ["CWE-798"]
8673
+ },
8674
+ {
8675
+ id: "SEC-SEC-006",
8676
+ name: "Anthropic API Key",
8677
+ category: "secrets",
8678
+ severity: "error",
8679
+ confidence: "high",
8680
+ patterns: [/sk-ant-api\d{2}-[A-Za-z0-9_-]{20,}/],
8681
+ message: "Hardcoded Anthropic API key detected",
8682
+ remediation: "Use environment variables: process.env.ANTHROPIC_API_KEY",
8683
+ references: ["CWE-798"]
8684
+ },
8685
+ {
8686
+ id: "SEC-SEC-007",
8687
+ name: "OpenAI API Key",
8688
+ category: "secrets",
8689
+ severity: "error",
8690
+ confidence: "high",
8691
+ patterns: [/sk-proj-[A-Za-z0-9_-]{20,}/],
8692
+ message: "Hardcoded OpenAI API key detected",
8693
+ remediation: "Use environment variables: process.env.OPENAI_API_KEY",
8694
+ references: ["CWE-798"]
8695
+ },
8696
+ {
8697
+ id: "SEC-SEC-008",
8698
+ name: "Google API Key",
8699
+ category: "secrets",
8700
+ severity: "error",
8701
+ confidence: "high",
8702
+ patterns: [/AIza[A-Za-z0-9_-]{35}/],
8703
+ message: "Hardcoded Google API key detected",
8704
+ remediation: "Use environment variables or a secrets manager for Google API keys",
8705
+ references: ["CWE-798"]
8706
+ },
8707
+ {
8708
+ id: "SEC-SEC-009",
8709
+ name: "GitHub Personal Access Token",
8710
+ category: "secrets",
8711
+ severity: "error",
8712
+ confidence: "high",
8713
+ patterns: [/gh[pous]_[A-Za-z0-9_]{36,}/],
8714
+ message: "Hardcoded GitHub personal access token detected",
8715
+ remediation: "Use environment variables: process.env.GITHUB_TOKEN",
8716
+ references: ["CWE-798"]
8717
+ },
8718
+ {
8719
+ id: "SEC-SEC-010",
8720
+ name: "Stripe Live Key",
8721
+ category: "secrets",
8722
+ severity: "error",
8723
+ confidence: "high",
8724
+ patterns: [/\b[spr]k_live_[A-Za-z0-9]{24,}/],
8725
+ message: "Hardcoded Stripe live key detected",
8726
+ remediation: "Use environment variables for Stripe keys; never commit live keys",
8727
+ references: ["CWE-798"]
8728
+ },
8729
+ {
8730
+ id: "SEC-SEC-011",
8731
+ name: "Database Connection String with Credentials",
8732
+ category: "secrets",
8733
+ severity: "error",
8734
+ confidence: "high",
8735
+ patterns: [/(?:postgres|mysql|mongodb|redis|amqp|mssql)(?:\+\w+)?:\/\/[^/\s:]+:[^@/\s]+@/i],
8736
+ message: "Database connection string with embedded credentials detected",
8737
+ remediation: "Use environment variables for connection strings; separate credentials from URIs",
8738
+ references: ["CWE-798"]
8245
8739
  }
8246
8740
  ];
8247
8741
 
@@ -8428,6 +8922,158 @@ var deserializationRules = [
8428
8922
  }
8429
8923
  ];
8430
8924
 
8925
+ // src/security/rules/agent-config.ts
8926
+ var agentConfigRules = [
8927
+ {
8928
+ id: "SEC-AGT-001",
8929
+ name: "Hidden Unicode Characters",
8930
+ category: "agent-config",
8931
+ severity: "error",
8932
+ confidence: "high",
8933
+ patterns: [/\u200B|\u200C|\u200D|\uFEFF|\u2060/],
8934
+ fileGlob: "**/CLAUDE.md,**/AGENTS.md,**/*.yaml",
8935
+ message: "Hidden zero-width Unicode characters detected in agent configuration",
8936
+ remediation: "Remove invisible Unicode characters; they may hide malicious instructions",
8937
+ references: ["CWE-116"]
8938
+ },
8939
+ {
8940
+ id: "SEC-AGT-002",
8941
+ name: "URL Execution Directives",
8942
+ category: "agent-config",
8943
+ severity: "warning",
8944
+ confidence: "medium",
8945
+ patterns: [/\b(?:curl|wget)\s+\S+/i, /\bfetch\s*\(/i],
8946
+ fileGlob: "**/CLAUDE.md,**/AGENTS.md",
8947
+ message: "URL execution directive found in agent configuration",
8948
+ remediation: "Avoid instructing agents to download and execute remote content",
8949
+ references: ["CWE-94"]
8950
+ },
8951
+ {
8952
+ id: "SEC-AGT-003",
8953
+ name: "Wildcard Tool Permissions",
8954
+ category: "agent-config",
8955
+ severity: "warning",
8956
+ confidence: "high",
8957
+ patterns: [/(?:Bash|Write|Edit)\s*\(\s*\*\s*\)/],
8958
+ fileGlob: "**/.claude/**,**/settings*.json",
8959
+ message: "Wildcard tool permissions grant unrestricted access",
8960
+ remediation: "Scope tool permissions to specific patterns instead of wildcards",
8961
+ references: ["CWE-250"]
8962
+ },
8963
+ {
8964
+ id: "SEC-AGT-004",
8965
+ name: "Auto-approve Patterns",
8966
+ category: "agent-config",
8967
+ severity: "warning",
8968
+ confidence: "high",
8969
+ patterns: [/\bautoApprove\b/i, /\bauto_approve\b/i],
8970
+ fileGlob: "**/.claude/**,**/.mcp.json",
8971
+ message: "Auto-approve configuration bypasses human review of tool calls",
8972
+ remediation: "Review auto-approved tools carefully; prefer explicit approval for destructive operations",
8973
+ references: ["CWE-862"]
8974
+ },
8975
+ {
8976
+ id: "SEC-AGT-005",
8977
+ name: "Prompt Injection Surface",
8978
+ category: "agent-config",
8979
+ severity: "warning",
8980
+ confidence: "medium",
8981
+ patterns: [/\$\{[^}]*\}/, /\{\{[^}]*\}\}/],
8982
+ fileGlob: "**/skill.yaml",
8983
+ message: "Template interpolation syntax in skill YAML may enable prompt injection",
8984
+ remediation: "Avoid dynamic interpolation in skill descriptions; use static text",
8985
+ references: ["CWE-94"]
8986
+ },
8987
+ {
8988
+ id: "SEC-AGT-006",
8989
+ name: "Permission Bypass Flags",
8990
+ category: "agent-config",
8991
+ severity: "error",
8992
+ confidence: "high",
8993
+ patterns: [/--dangerously-skip-permissions/, /--no-verify/],
8994
+ fileGlob: "**/CLAUDE.md,**/AGENTS.md,**/.claude/**",
8995
+ message: "Permission bypass flag detected in agent configuration",
8996
+ remediation: "Remove flags that bypass safety checks; they undermine enforcement",
8997
+ references: ["CWE-863"]
8998
+ },
8999
+ {
9000
+ id: "SEC-AGT-007",
9001
+ name: "Hook Injection Surface",
9002
+ category: "agent-config",
9003
+ severity: "error",
9004
+ confidence: "low",
9005
+ patterns: [/\$\(/, /`[^`]+`/, /\s&&\s/, /\s\|\|\s/],
9006
+ fileGlob: "**/settings*.json,**/hooks.json",
9007
+ message: "Shell metacharacters in hook commands may enable command injection",
9008
+ remediation: "Use simple, single-command hooks without shell operators; chain logic inside the script",
9009
+ references: ["CWE-78"]
9010
+ }
9011
+ ];
9012
+
9013
+ // src/security/rules/mcp.ts
9014
+ var mcpRules = [
9015
+ {
9016
+ id: "SEC-MCP-001",
9017
+ name: "Hardcoded MCP Secrets",
9018
+ category: "mcp",
9019
+ severity: "error",
9020
+ confidence: "medium",
9021
+ patterns: [/(?:API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)\s*["']?\s*:\s*["'][^"']{8,}["']/i],
9022
+ fileGlob: "**/.mcp.json",
9023
+ message: "Hardcoded secret detected in MCP server configuration",
9024
+ remediation: "Use environment variable references instead of inline secrets in .mcp.json",
9025
+ references: ["CWE-798"]
9026
+ },
9027
+ {
9028
+ id: "SEC-MCP-002",
9029
+ name: "Shell Injection in MCP Args",
9030
+ category: "mcp",
9031
+ severity: "error",
9032
+ confidence: "medium",
9033
+ patterns: [/\$\(/, /`[^`]+`/],
9034
+ fileGlob: "**/.mcp.json",
9035
+ message: "Shell metacharacters detected in MCP server arguments",
9036
+ remediation: "Use literal argument values; avoid shell interpolation in MCP args",
9037
+ references: ["CWE-78"]
9038
+ },
9039
+ {
9040
+ id: "SEC-MCP-003",
9041
+ name: "Network Exposure",
9042
+ category: "mcp",
9043
+ severity: "warning",
9044
+ confidence: "high",
9045
+ patterns: [/0\.0\.0\.0/, /["']\*["']\s*:\s*\d/, /host["']?\s*:\s*["']\*["']/i],
9046
+ fileGlob: "**/.mcp.json",
9047
+ message: "MCP server binding to all network interfaces (0.0.0.0 or wildcard *)",
9048
+ remediation: "Bind to 127.0.0.1 or localhost to restrict access to local machine",
9049
+ references: ["CWE-668"]
9050
+ },
9051
+ {
9052
+ id: "SEC-MCP-004",
9053
+ name: "Typosquatting Vector",
9054
+ category: "mcp",
9055
+ severity: "warning",
9056
+ confidence: "medium",
9057
+ patterns: [/\bnpx\s+(?:-y|--yes)\b/],
9058
+ fileGlob: "**/.mcp.json",
9059
+ message: "npx -y auto-installs packages without confirmation, enabling typosquatting",
9060
+ remediation: "Pin exact package versions or install packages explicitly before use",
9061
+ references: ["CWE-427"]
9062
+ },
9063
+ {
9064
+ id: "SEC-MCP-005",
9065
+ name: "Unencrypted Transport",
9066
+ category: "mcp",
9067
+ severity: "warning",
9068
+ confidence: "medium",
9069
+ patterns: [/http:\/\/(?!localhost\b|127\.0\.0\.1\b)/],
9070
+ fileGlob: "**/.mcp.json",
9071
+ message: "Unencrypted HTTP transport detected for MCP server connection",
9072
+ remediation: "Use https:// for all non-localhost MCP server connections",
9073
+ references: ["CWE-319"]
9074
+ }
9075
+ ];
9076
+
8431
9077
  // src/security/rules/stack/node.ts
8432
9078
  var nodeRules = [
8433
9079
  {
@@ -8555,7 +9201,9 @@ var SecurityScanner = class {
8555
9201
  ...cryptoRules,
8556
9202
  ...pathTraversalRules,
8557
9203
  ...networkRules,
8558
- ...deserializationRules
9204
+ ...deserializationRules,
9205
+ ...agentConfigRules,
9206
+ ...mcpRules
8559
9207
  ]);
8560
9208
  this.registry.registerAll([...nodeRules, ...expressRules, ...reactRules, ...goRules]);
8561
9209
  this.activeRules = this.registry.getAll();
@@ -8564,6 +9212,12 @@ var SecurityScanner = class {
8564
9212
  const stacks = detectStack(projectRoot);
8565
9213
  this.activeRules = this.registry.getForStacks(stacks.length > 0 ? stacks : []);
8566
9214
  }
9215
+ /**
9216
+ * Scan raw content against all active rules. Note: this method does NOT apply
9217
+ * fileGlob filtering — every active rule is evaluated regardless of filePath.
9218
+ * If you are scanning a specific file and want fileGlob-based rule filtering,
9219
+ * use {@link scanFile} instead.
9220
+ */
8567
9221
  scanContent(content, filePath, startLine = 1) {
8568
9222
  if (!this.config.enabled) return [];
8569
9223
  const findings = [];
@@ -8605,8 +9259,52 @@ var SecurityScanner = class {
8605
9259
  }
8606
9260
  async scanFile(filePath) {
8607
9261
  if (!this.config.enabled) return [];
8608
- const content = await fs17.readFile(filePath, "utf-8");
8609
- return this.scanContent(content, filePath, 1);
9262
+ const content = await fs18.readFile(filePath, "utf-8");
9263
+ return this.scanContentForFile(content, filePath, 1);
9264
+ }
9265
+ scanContentForFile(content, filePath, startLine = 1) {
9266
+ if (!this.config.enabled) return [];
9267
+ const findings = [];
9268
+ const lines = content.split("\n");
9269
+ const applicableRules = this.activeRules.filter((rule) => {
9270
+ if (!rule.fileGlob) return true;
9271
+ const globs = rule.fileGlob.split(",").map((g) => g.trim());
9272
+ return globs.some((glob2) => (0, import_minimatch5.minimatch)(filePath, glob2, { dot: true }));
9273
+ });
9274
+ for (const rule of applicableRules) {
9275
+ const resolved = resolveRuleSeverity(
9276
+ rule.id,
9277
+ rule.severity,
9278
+ this.config.rules ?? {},
9279
+ this.config.strict
9280
+ );
9281
+ if (resolved === "off") continue;
9282
+ for (let i = 0; i < lines.length; i++) {
9283
+ const line = lines[i] ?? "";
9284
+ if (line.includes("harness-ignore") && line.includes(rule.id)) continue;
9285
+ for (const pattern of rule.patterns) {
9286
+ pattern.lastIndex = 0;
9287
+ if (pattern.test(line)) {
9288
+ findings.push({
9289
+ ruleId: rule.id,
9290
+ ruleName: rule.name,
9291
+ category: rule.category,
9292
+ severity: resolved,
9293
+ confidence: rule.confidence,
9294
+ file: filePath,
9295
+ line: startLine + i,
9296
+ match: line.trim(),
9297
+ context: line,
9298
+ message: rule.message,
9299
+ remediation: rule.remediation,
9300
+ ...rule.references ? { references: rule.references } : {}
9301
+ });
9302
+ break;
9303
+ }
9304
+ }
9305
+ }
9306
+ }
9307
+ return findings;
8610
9308
  }
8611
9309
  async scanFiles(filePaths) {
8612
9310
  const allFindings = [];
@@ -8630,7 +9328,7 @@ var SecurityScanner = class {
8630
9328
  };
8631
9329
 
8632
9330
  // src/ci/check-orchestrator.ts
8633
- var path14 = __toESM(require("path"));
9331
+ var path15 = __toESM(require("path"));
8634
9332
  var ALL_CHECKS = [
8635
9333
  "validate",
8636
9334
  "deps",
@@ -8643,7 +9341,7 @@ var ALL_CHECKS = [
8643
9341
  ];
8644
9342
  async function runValidateCheck(projectRoot, config) {
8645
9343
  const issues = [];
8646
- const agentsPath = path14.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9344
+ const agentsPath = path15.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8647
9345
  const result = await validateAgentsMap(agentsPath);
8648
9346
  if (!result.ok) {
8649
9347
  issues.push({ severity: "error", message: result.error.message });
@@ -8700,7 +9398,7 @@ async function runDepsCheck(projectRoot, config) {
8700
9398
  }
8701
9399
  async function runDocsCheck(projectRoot, config) {
8702
9400
  const issues = [];
8703
- const docsDir = path14.join(projectRoot, config.docsDir ?? "docs");
9401
+ const docsDir = path15.join(projectRoot, config.docsDir ?? "docs");
8704
9402
  const entropyConfig = config.entropy || {};
8705
9403
  const result = await checkDocCoverage("project", {
8706
9404
  docsDir,
@@ -8978,7 +9676,7 @@ async function runCIChecks(input) {
8978
9676
  }
8979
9677
 
8980
9678
  // src/review/mechanical-checks.ts
8981
- var path15 = __toESM(require("path"));
9679
+ var path16 = __toESM(require("path"));
8982
9680
  async function runMechanicalChecks(options) {
8983
9681
  const { projectRoot, config, skip = [], changedFiles } = options;
8984
9682
  const findings = [];
@@ -8990,7 +9688,7 @@ async function runMechanicalChecks(options) {
8990
9688
  };
8991
9689
  if (!skip.includes("validate")) {
8992
9690
  try {
8993
- const agentsPath = path15.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
9691
+ const agentsPath = path16.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
8994
9692
  const result = await validateAgentsMap(agentsPath);
8995
9693
  if (!result.ok) {
8996
9694
  statuses.validate = "fail";
@@ -9027,7 +9725,7 @@ async function runMechanicalChecks(options) {
9027
9725
  statuses.validate = "fail";
9028
9726
  findings.push({
9029
9727
  tool: "validate",
9030
- file: path15.join(projectRoot, "AGENTS.md"),
9728
+ file: path16.join(projectRoot, "AGENTS.md"),
9031
9729
  message: err instanceof Error ? err.message : String(err),
9032
9730
  severity: "error"
9033
9731
  });
@@ -9091,7 +9789,7 @@ async function runMechanicalChecks(options) {
9091
9789
  (async () => {
9092
9790
  const localFindings = [];
9093
9791
  try {
9094
- const docsDir = path15.join(projectRoot, config.docsDir ?? "docs");
9792
+ const docsDir = path16.join(projectRoot, config.docsDir ?? "docs");
9095
9793
  const result = await checkDocCoverage("project", { docsDir });
9096
9794
  if (!result.ok) {
9097
9795
  statuses["check-docs"] = "warn";
@@ -9118,7 +9816,7 @@ async function runMechanicalChecks(options) {
9118
9816
  statuses["check-docs"] = "warn";
9119
9817
  localFindings.push({
9120
9818
  tool: "check-docs",
9121
- file: path15.join(projectRoot, "docs"),
9819
+ file: path16.join(projectRoot, "docs"),
9122
9820
  message: err instanceof Error ? err.message : String(err),
9123
9821
  severity: "warning"
9124
9822
  });
@@ -9266,7 +9964,7 @@ function detectChangeType(commitMessage, diff2) {
9266
9964
  }
9267
9965
 
9268
9966
  // src/review/context-scoper.ts
9269
- var path16 = __toESM(require("path"));
9967
+ var path17 = __toESM(require("path"));
9270
9968
  var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
9271
9969
  var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
9272
9970
  function computeContextBudget(diffLines) {
@@ -9274,18 +9972,18 @@ function computeContextBudget(diffLines) {
9274
9972
  return diffLines;
9275
9973
  }
9276
9974
  function isWithinProject(absPath, projectRoot) {
9277
- const resolvedRoot = path16.resolve(projectRoot) + path16.sep;
9278
- const resolvedPath = path16.resolve(absPath);
9279
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path16.resolve(projectRoot);
9975
+ const resolvedRoot = path17.resolve(projectRoot) + path17.sep;
9976
+ const resolvedPath = path17.resolve(absPath);
9977
+ return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path17.resolve(projectRoot);
9280
9978
  }
9281
9979
  async function readContextFile(projectRoot, filePath, reason) {
9282
- const absPath = path16.isAbsolute(filePath) ? filePath : path16.join(projectRoot, filePath);
9980
+ const absPath = path17.isAbsolute(filePath) ? filePath : path17.join(projectRoot, filePath);
9283
9981
  if (!isWithinProject(absPath, projectRoot)) return null;
9284
9982
  const result = await readFileContent(absPath);
9285
9983
  if (!result.ok) return null;
9286
9984
  const content = result.value;
9287
9985
  const lines = content.split("\n").length;
9288
- const relPath = path16.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
9986
+ const relPath = path17.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
9289
9987
  return { path: relPath, content, reason, lines };
9290
9988
  }
9291
9989
  function extractImportSources2(content) {
@@ -9300,18 +9998,18 @@ function extractImportSources2(content) {
9300
9998
  }
9301
9999
  async function resolveImportPath2(projectRoot, fromFile, importSource) {
9302
10000
  if (!importSource.startsWith(".")) return null;
9303
- const fromDir = path16.dirname(path16.join(projectRoot, fromFile));
9304
- const basePath = path16.resolve(fromDir, importSource);
10001
+ const fromDir = path17.dirname(path17.join(projectRoot, fromFile));
10002
+ const basePath = path17.resolve(fromDir, importSource);
9305
10003
  if (!isWithinProject(basePath, projectRoot)) return null;
9306
10004
  const relBase = relativePosix(projectRoot, basePath);
9307
10005
  const candidates = [
9308
10006
  relBase + ".ts",
9309
10007
  relBase + ".tsx",
9310
10008
  relBase + ".mts",
9311
- path16.join(relBase, "index.ts")
10009
+ path17.join(relBase, "index.ts")
9312
10010
  ];
9313
10011
  for (const candidate of candidates) {
9314
- const absCandidate = path16.join(projectRoot, candidate);
10012
+ const absCandidate = path17.join(projectRoot, candidate);
9315
10013
  if (await fileExists(absCandidate)) {
9316
10014
  return candidate;
9317
10015
  }
@@ -9319,7 +10017,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
9319
10017
  return null;
9320
10018
  }
9321
10019
  async function findTestFiles(projectRoot, sourceFile) {
9322
- const baseName = path16.basename(sourceFile, path16.extname(sourceFile));
10020
+ const baseName = path17.basename(sourceFile, path17.extname(sourceFile));
9323
10021
  const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
9324
10022
  const results = await findFiles(pattern, projectRoot);
9325
10023
  return results.map((f) => relativePosix(projectRoot, f));
@@ -10128,7 +10826,7 @@ async function fanOutReview(options) {
10128
10826
  }
10129
10827
 
10130
10828
  // src/review/validate-findings.ts
10131
- var path17 = __toESM(require("path"));
10829
+ var path18 = __toESM(require("path"));
10132
10830
  var DOWNGRADE_MAP = {
10133
10831
  critical: "important",
10134
10832
  important: "suggestion",
@@ -10149,7 +10847,7 @@ function normalizePath(filePath, projectRoot) {
10149
10847
  let normalized = filePath;
10150
10848
  normalized = normalized.replace(/\\/g, "/");
10151
10849
  const normalizedRoot = projectRoot.replace(/\\/g, "/");
10152
- if (path17.isAbsolute(normalized)) {
10850
+ if (path18.isAbsolute(normalized)) {
10153
10851
  const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
10154
10852
  if (normalized.startsWith(root)) {
10155
10853
  normalized = normalized.slice(root.length);
@@ -10174,12 +10872,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
10174
10872
  while ((match = importRegex.exec(content)) !== null) {
10175
10873
  const importPath = match[1];
10176
10874
  if (!importPath.startsWith(".")) continue;
10177
- const dir = path17.dirname(current.file);
10178
- let resolved = path17.join(dir, importPath).replace(/\\/g, "/");
10875
+ const dir = path18.dirname(current.file);
10876
+ let resolved = path18.join(dir, importPath).replace(/\\/g, "/");
10179
10877
  if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
10180
10878
  resolved += ".ts";
10181
10879
  }
10182
- resolved = path17.normalize(resolved).replace(/\\/g, "/");
10880
+ resolved = path18.normalize(resolved).replace(/\\/g, "/");
10183
10881
  if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
10184
10882
  queue.push({ file: resolved, depth: current.depth + 1 });
10185
10883
  }
@@ -10196,7 +10894,7 @@ async function validateFindings(options) {
10196
10894
  if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
10197
10895
  continue;
10198
10896
  }
10199
- const absoluteFile = path17.isAbsolute(finding.file) ? finding.file : path17.join(projectRoot, finding.file).replace(/\\/g, "/");
10897
+ const absoluteFile = path18.isAbsolute(finding.file) ? finding.file : path18.join(projectRoot, finding.file).replace(/\\/g, "/");
10200
10898
  if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
10201
10899
  continue;
10202
10900
  }
@@ -10824,7 +11522,7 @@ function parseRoadmap(markdown) {
10824
11522
  if (!fmMatch) {
10825
11523
  return (0, import_types19.Err)(new Error("Missing or malformed YAML frontmatter"));
10826
11524
  }
10827
- const fmResult = parseFrontmatter(fmMatch[1]);
11525
+ const fmResult = parseFrontmatter2(fmMatch[1]);
10828
11526
  if (!fmResult.ok) return fmResult;
10829
11527
  const body = markdown.slice(fmMatch[0].length);
10830
11528
  const milestonesResult = parseMilestones(body);
@@ -10834,7 +11532,7 @@ function parseRoadmap(markdown) {
10834
11532
  milestones: milestonesResult.value
10835
11533
  });
10836
11534
  }
10837
- function parseFrontmatter(raw) {
11535
+ function parseFrontmatter2(raw) {
10838
11536
  const lines = raw.split("\n");
10839
11537
  const map = /* @__PURE__ */ new Map();
10840
11538
  for (const line of lines) {
@@ -11000,8 +11698,8 @@ function serializeFeature(feature) {
11000
11698
  }
11001
11699
 
11002
11700
  // src/roadmap/sync.ts
11003
- var fs18 = __toESM(require("fs"));
11004
- var path18 = __toESM(require("path"));
11701
+ var fs19 = __toESM(require("fs"));
11702
+ var path19 = __toESM(require("path"));
11005
11703
  var import_types20 = require("@harness-engineering/types");
11006
11704
  function inferStatus(feature, projectPath, allFeatures) {
11007
11705
  if (feature.blockedBy.length > 0) {
@@ -11016,10 +11714,10 @@ function inferStatus(feature, projectPath, allFeatures) {
11016
11714
  const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
11017
11715
  const useRootState = featuresWithPlans.length <= 1;
11018
11716
  if (useRootState) {
11019
- const rootStatePath = path18.join(projectPath, ".harness", "state.json");
11020
- if (fs18.existsSync(rootStatePath)) {
11717
+ const rootStatePath = path19.join(projectPath, ".harness", "state.json");
11718
+ if (fs19.existsSync(rootStatePath)) {
11021
11719
  try {
11022
- const raw = fs18.readFileSync(rootStatePath, "utf-8");
11720
+ const raw = fs19.readFileSync(rootStatePath, "utf-8");
11023
11721
  const state = JSON.parse(raw);
11024
11722
  if (state.progress) {
11025
11723
  for (const status of Object.values(state.progress)) {
@@ -11030,16 +11728,16 @@ function inferStatus(feature, projectPath, allFeatures) {
11030
11728
  }
11031
11729
  }
11032
11730
  }
11033
- const sessionsDir = path18.join(projectPath, ".harness", "sessions");
11034
- if (fs18.existsSync(sessionsDir)) {
11731
+ const sessionsDir = path19.join(projectPath, ".harness", "sessions");
11732
+ if (fs19.existsSync(sessionsDir)) {
11035
11733
  try {
11036
- const sessionDirs = fs18.readdirSync(sessionsDir, { withFileTypes: true });
11734
+ const sessionDirs = fs19.readdirSync(sessionsDir, { withFileTypes: true });
11037
11735
  for (const entry of sessionDirs) {
11038
11736
  if (!entry.isDirectory()) continue;
11039
- const autopilotPath = path18.join(sessionsDir, entry.name, "autopilot-state.json");
11040
- if (!fs18.existsSync(autopilotPath)) continue;
11737
+ const autopilotPath = path19.join(sessionsDir, entry.name, "autopilot-state.json");
11738
+ if (!fs19.existsSync(autopilotPath)) continue;
11041
11739
  try {
11042
- const raw = fs18.readFileSync(autopilotPath, "utf-8");
11740
+ const raw = fs19.readFileSync(autopilotPath, "utf-8");
11043
11741
  const autopilot = JSON.parse(raw);
11044
11742
  if (!autopilot.phases) continue;
11045
11743
  const linkedPhases = autopilot.phases.filter(
@@ -11069,17 +11767,26 @@ function inferStatus(feature, projectPath, allFeatures) {
11069
11767
  if (anyStarted) return "in-progress";
11070
11768
  return null;
11071
11769
  }
11770
+ var STATUS_RANK = {
11771
+ backlog: 0,
11772
+ planned: 1,
11773
+ blocked: 1,
11774
+ // lateral to planned — sync can move to/from blocked freely
11775
+ "in-progress": 2,
11776
+ done: 3
11777
+ };
11778
+ function isRegression(from, to) {
11779
+ return STATUS_RANK[to] < STATUS_RANK[from];
11780
+ }
11072
11781
  function syncRoadmap(options) {
11073
11782
  const { projectPath, roadmap, forceSync } = options;
11074
- const isManuallyEdited = new Date(roadmap.frontmatter.lastManualEdit) > new Date(roadmap.frontmatter.lastSynced);
11075
- const skipOverride = isManuallyEdited && !forceSync;
11076
11783
  const allFeatures = roadmap.milestones.flatMap((m) => m.features);
11077
11784
  const changes = [];
11078
11785
  for (const feature of allFeatures) {
11079
- if (skipOverride) continue;
11080
11786
  const inferred = inferStatus(feature, projectPath, allFeatures);
11081
11787
  if (inferred === null) continue;
11082
11788
  if (inferred === feature.status) continue;
11789
+ if (!forceSync && isRegression(feature.status, inferred)) continue;
11083
11790
  changes.push({
11084
11791
  feature: feature.name,
11085
11792
  from: feature.status,
@@ -11088,48 +11795,60 @@ function syncRoadmap(options) {
11088
11795
  }
11089
11796
  return (0, import_types20.Ok)(changes);
11090
11797
  }
11798
+ function applySyncChanges(roadmap, changes) {
11799
+ for (const change of changes) {
11800
+ for (const m of roadmap.milestones) {
11801
+ const feature = m.features.find((f) => f.name.toLowerCase() === change.feature.toLowerCase());
11802
+ if (feature) {
11803
+ feature.status = change.to;
11804
+ break;
11805
+ }
11806
+ }
11807
+ }
11808
+ roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
11809
+ }
11091
11810
 
11092
11811
  // src/interaction/types.ts
11093
- var import_zod7 = require("zod");
11094
- var InteractionTypeSchema = import_zod7.z.enum(["question", "confirmation", "transition"]);
11095
- var QuestionSchema = import_zod7.z.object({
11096
- text: import_zod7.z.string(),
11097
- options: import_zod7.z.array(import_zod7.z.string()).optional(),
11098
- default: import_zod7.z.string().optional()
11812
+ var import_zod8 = require("zod");
11813
+ var InteractionTypeSchema = import_zod8.z.enum(["question", "confirmation", "transition"]);
11814
+ var QuestionSchema = import_zod8.z.object({
11815
+ text: import_zod8.z.string(),
11816
+ options: import_zod8.z.array(import_zod8.z.string()).optional(),
11817
+ default: import_zod8.z.string().optional()
11099
11818
  });
11100
- var ConfirmationSchema = import_zod7.z.object({
11101
- text: import_zod7.z.string(),
11102
- context: import_zod7.z.string()
11819
+ var ConfirmationSchema = import_zod8.z.object({
11820
+ text: import_zod8.z.string(),
11821
+ context: import_zod8.z.string()
11103
11822
  });
11104
- var TransitionSchema = import_zod7.z.object({
11105
- completedPhase: import_zod7.z.string(),
11106
- suggestedNext: import_zod7.z.string(),
11107
- reason: import_zod7.z.string(),
11108
- artifacts: import_zod7.z.array(import_zod7.z.string()),
11109
- requiresConfirmation: import_zod7.z.boolean(),
11110
- summary: import_zod7.z.string()
11823
+ var TransitionSchema = import_zod8.z.object({
11824
+ completedPhase: import_zod8.z.string(),
11825
+ suggestedNext: import_zod8.z.string(),
11826
+ reason: import_zod8.z.string(),
11827
+ artifacts: import_zod8.z.array(import_zod8.z.string()),
11828
+ requiresConfirmation: import_zod8.z.boolean(),
11829
+ summary: import_zod8.z.string()
11111
11830
  });
11112
- var EmitInteractionInputSchema = import_zod7.z.object({
11113
- path: import_zod7.z.string(),
11831
+ var EmitInteractionInputSchema = import_zod8.z.object({
11832
+ path: import_zod8.z.string(),
11114
11833
  type: InteractionTypeSchema,
11115
- stream: import_zod7.z.string().optional(),
11834
+ stream: import_zod8.z.string().optional(),
11116
11835
  question: QuestionSchema.optional(),
11117
11836
  confirmation: ConfirmationSchema.optional(),
11118
11837
  transition: TransitionSchema.optional()
11119
11838
  });
11120
11839
 
11121
11840
  // src/blueprint/scanner.ts
11122
- var fs19 = __toESM(require("fs/promises"));
11123
- var path19 = __toESM(require("path"));
11841
+ var fs20 = __toESM(require("fs/promises"));
11842
+ var path20 = __toESM(require("path"));
11124
11843
  var ProjectScanner = class {
11125
11844
  constructor(rootDir) {
11126
11845
  this.rootDir = rootDir;
11127
11846
  }
11128
11847
  async scan() {
11129
- let projectName = path19.basename(this.rootDir);
11848
+ let projectName = path20.basename(this.rootDir);
11130
11849
  try {
11131
- const pkgPath = path19.join(this.rootDir, "package.json");
11132
- const pkgRaw = await fs19.readFile(pkgPath, "utf-8");
11850
+ const pkgPath = path20.join(this.rootDir, "package.json");
11851
+ const pkgRaw = await fs20.readFile(pkgPath, "utf-8");
11133
11852
  const pkg = JSON.parse(pkgRaw);
11134
11853
  if (pkg.name) projectName = pkg.name;
11135
11854
  } catch {
@@ -11170,8 +11889,8 @@ var ProjectScanner = class {
11170
11889
  };
11171
11890
 
11172
11891
  // src/blueprint/generator.ts
11173
- var fs20 = __toESM(require("fs/promises"));
11174
- var path20 = __toESM(require("path"));
11892
+ var fs21 = __toESM(require("fs/promises"));
11893
+ var path21 = __toESM(require("path"));
11175
11894
  var ejs = __toESM(require("ejs"));
11176
11895
 
11177
11896
  // src/blueprint/templates.ts
@@ -11255,19 +11974,19 @@ var BlueprintGenerator = class {
11255
11974
  styles: STYLES,
11256
11975
  scripts: SCRIPTS
11257
11976
  });
11258
- await fs20.mkdir(options.outputDir, { recursive: true });
11259
- await fs20.writeFile(path20.join(options.outputDir, "index.html"), html);
11977
+ await fs21.mkdir(options.outputDir, { recursive: true });
11978
+ await fs21.writeFile(path21.join(options.outputDir, "index.html"), html);
11260
11979
  }
11261
11980
  };
11262
11981
 
11263
11982
  // src/update-checker.ts
11264
- var fs21 = __toESM(require("fs"));
11265
- var path21 = __toESM(require("path"));
11983
+ var fs22 = __toESM(require("fs"));
11984
+ var path22 = __toESM(require("path"));
11266
11985
  var os = __toESM(require("os"));
11267
11986
  var import_child_process3 = require("child_process");
11268
11987
  function getStatePath() {
11269
11988
  const home = process.env["HOME"] || os.homedir();
11270
- return path21.join(home, ".harness", "update-check.json");
11989
+ return path22.join(home, ".harness", "update-check.json");
11271
11990
  }
11272
11991
  function isUpdateCheckEnabled(configInterval) {
11273
11992
  if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
@@ -11280,7 +11999,7 @@ function shouldRunCheck(state, intervalMs) {
11280
11999
  }
11281
12000
  function readCheckState() {
11282
12001
  try {
11283
- const raw = fs21.readFileSync(getStatePath(), "utf-8");
12002
+ const raw = fs22.readFileSync(getStatePath(), "utf-8");
11284
12003
  const parsed = JSON.parse(raw);
11285
12004
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
11286
12005
  const state = parsed;
@@ -11297,7 +12016,7 @@ function readCheckState() {
11297
12016
  }
11298
12017
  function spawnBackgroundCheck(currentVersion) {
11299
12018
  const statePath = getStatePath();
11300
- const stateDir = path21.dirname(statePath);
12019
+ const stateDir = path22.dirname(statePath);
11301
12020
  const script = `
11302
12021
  const { execSync } = require('child_process');
11303
12022
  const fs = require('fs');
@@ -11350,8 +12069,411 @@ function getUpdateNotification(currentVersion) {
11350
12069
  Run "harness update" to upgrade.`;
11351
12070
  }
11352
12071
 
12072
+ // src/code-nav/types.ts
12073
+ var EXTENSION_MAP = {
12074
+ ".ts": "typescript",
12075
+ ".tsx": "typescript",
12076
+ ".mts": "typescript",
12077
+ ".cts": "typescript",
12078
+ ".js": "javascript",
12079
+ ".jsx": "javascript",
12080
+ ".mjs": "javascript",
12081
+ ".cjs": "javascript",
12082
+ ".py": "python"
12083
+ };
12084
+ function detectLanguage(filePath) {
12085
+ const ext = filePath.slice(filePath.lastIndexOf("."));
12086
+ return EXTENSION_MAP[ext] ?? null;
12087
+ }
12088
+
12089
+ // src/code-nav/parser.ts
12090
+ var import_web_tree_sitter = __toESM(require("web-tree-sitter"));
12091
+ var import_meta = {};
12092
+ var parserCache = /* @__PURE__ */ new Map();
12093
+ var initialized = false;
12094
+ var GRAMMAR_MAP = {
12095
+ typescript: "tree-sitter-typescript",
12096
+ javascript: "tree-sitter-javascript",
12097
+ python: "tree-sitter-python"
12098
+ };
12099
+ async function ensureInit() {
12100
+ if (!initialized) {
12101
+ await import_web_tree_sitter.default.init();
12102
+ initialized = true;
12103
+ }
12104
+ }
12105
+ async function resolveWasmPath(grammarName) {
12106
+ const { createRequire } = await import("module");
12107
+ const require2 = createRequire(import_meta.url ?? __filename);
12108
+ const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
12109
+ const path23 = await import("path");
12110
+ const pkgDir = path23.dirname(pkgPath);
12111
+ return path23.join(pkgDir, "out", `${grammarName}.wasm`);
12112
+ }
12113
+ async function loadLanguage(lang) {
12114
+ const grammarName = GRAMMAR_MAP[lang];
12115
+ const wasmPath = await resolveWasmPath(grammarName);
12116
+ return import_web_tree_sitter.default.Language.load(wasmPath);
12117
+ }
12118
+ async function getParser(lang) {
12119
+ const cached = parserCache.get(lang);
12120
+ if (cached) return cached;
12121
+ await ensureInit();
12122
+ const parser = new import_web_tree_sitter.default();
12123
+ const language = await loadLanguage(lang);
12124
+ parser.setLanguage(language);
12125
+ parserCache.set(lang, parser);
12126
+ return parser;
12127
+ }
12128
+ async function parseFile(filePath) {
12129
+ const lang = detectLanguage(filePath);
12130
+ if (!lang) {
12131
+ return (0, import_types.Err)({
12132
+ code: "UNSUPPORTED_LANGUAGE",
12133
+ message: `Unsupported file extension: ${filePath}`
12134
+ });
12135
+ }
12136
+ const contentResult = await readFileContent(filePath);
12137
+ if (!contentResult.ok) {
12138
+ return (0, import_types.Err)({
12139
+ code: "FILE_NOT_FOUND",
12140
+ message: `Cannot read file: ${filePath}`
12141
+ });
12142
+ }
12143
+ try {
12144
+ const parser = await getParser(lang);
12145
+ const tree = parser.parse(contentResult.value);
12146
+ return (0, import_types.Ok)({ tree, language: lang, source: contentResult.value, filePath });
12147
+ } catch (e) {
12148
+ return (0, import_types.Err)({
12149
+ code: "PARSE_FAILED",
12150
+ message: `Tree-sitter parse failed for ${filePath}: ${e.message}`
12151
+ });
12152
+ }
12153
+ }
12154
+ function resetParserCache() {
12155
+ parserCache.clear();
12156
+ initialized = false;
12157
+ }
12158
+
12159
+ // src/code-nav/outline.ts
12160
+ var TOP_LEVEL_TYPES = {
12161
+ typescript: {
12162
+ function_declaration: "function",
12163
+ class_declaration: "class",
12164
+ interface_declaration: "interface",
12165
+ type_alias_declaration: "type",
12166
+ lexical_declaration: "variable",
12167
+ variable_declaration: "variable",
12168
+ export_statement: "export",
12169
+ import_statement: "import",
12170
+ enum_declaration: "type"
12171
+ },
12172
+ javascript: {
12173
+ function_declaration: "function",
12174
+ class_declaration: "class",
12175
+ lexical_declaration: "variable",
12176
+ variable_declaration: "variable",
12177
+ export_statement: "export",
12178
+ import_statement: "import"
12179
+ },
12180
+ python: {
12181
+ function_definition: "function",
12182
+ class_definition: "class",
12183
+ assignment: "variable",
12184
+ import_statement: "import",
12185
+ import_from_statement: "import"
12186
+ }
12187
+ };
12188
+ var METHOD_TYPES = {
12189
+ typescript: ["method_definition", "public_field_definition"],
12190
+ javascript: ["method_definition"],
12191
+ python: ["function_definition"]
12192
+ };
12193
+ var IDENTIFIER_TYPES = /* @__PURE__ */ new Set(["identifier", "property_identifier", "type_identifier"]);
12194
+ function findIdentifier(node) {
12195
+ return node.childForFieldName("name") ?? node.children.find((c) => IDENTIFIER_TYPES.has(c.type)) ?? null;
12196
+ }
12197
+ function getVariableDeclarationName(node) {
12198
+ const declarator = node.children.find((c) => c.type === "variable_declarator");
12199
+ if (!declarator) return null;
12200
+ const id = findIdentifier(declarator);
12201
+ return id?.text ?? null;
12202
+ }
12203
+ function getExportName(node, source) {
12204
+ const decl = node.children.find(
12205
+ (c) => c.type !== "export" && c.type !== "default" && c.type !== "comment"
12206
+ );
12207
+ return decl ? getNodeName(decl, source) : "<anonymous>";
12208
+ }
12209
+ function getAssignmentName(node) {
12210
+ const left = node.childForFieldName("left") ?? node.children[0];
12211
+ return left?.text ?? "<anonymous>";
12212
+ }
12213
+ function getNodeName(node, source) {
12214
+ const id = findIdentifier(node);
12215
+ if (id) return id.text;
12216
+ const isVarDecl = node.type === "lexical_declaration" || node.type === "variable_declaration";
12217
+ if (isVarDecl) return getVariableDeclarationName(node) ?? "<anonymous>";
12218
+ if (node.type === "export_statement") return getExportName(node, source);
12219
+ if (node.type === "assignment") return getAssignmentName(node);
12220
+ return "<anonymous>";
12221
+ }
12222
+ function getSignature(node, source) {
12223
+ const startLine = node.startPosition.row;
12224
+ const lines = source.split("\n");
12225
+ return (lines[startLine] ?? "").trim();
12226
+ }
12227
+ function extractMethods(classNode, language, source, filePath) {
12228
+ const methodTypes = METHOD_TYPES[language] ?? [];
12229
+ const body = classNode.childForFieldName("body") ?? classNode.children.find((c) => c.type === "class_body" || c.type === "block");
12230
+ if (!body) return [];
12231
+ return body.children.filter((child) => methodTypes.includes(child.type)).map((child) => ({
12232
+ name: getNodeName(child, source),
12233
+ kind: "method",
12234
+ file: filePath,
12235
+ line: child.startPosition.row + 1,
12236
+ endLine: child.endPosition.row + 1,
12237
+ signature: getSignature(child, source)
12238
+ }));
12239
+ }
12240
+ function nodeToSymbol(node, kind, source, filePath) {
12241
+ return {
12242
+ name: getNodeName(node, source),
12243
+ kind,
12244
+ file: filePath,
12245
+ line: node.startPosition.row + 1,
12246
+ endLine: node.endPosition.row + 1,
12247
+ signature: getSignature(node, source)
12248
+ };
12249
+ }
12250
+ function processExportStatement(child, topLevelTypes, lang, source, filePath) {
12251
+ const declaration = child.children.find(
12252
+ (c) => c.type !== "export" && c.type !== "default" && c.type !== ";" && c.type !== "comment"
12253
+ );
12254
+ const kind = declaration ? topLevelTypes[declaration.type] : void 0;
12255
+ if (declaration && kind) {
12256
+ const sym = nodeToSymbol(child, kind, source, filePath);
12257
+ sym.name = getNodeName(declaration, source);
12258
+ if (kind === "class") {
12259
+ sym.children = extractMethods(declaration, lang, source, filePath);
12260
+ }
12261
+ return sym;
12262
+ }
12263
+ return nodeToSymbol(child, "export", source, filePath);
12264
+ }
12265
+ function extractSymbols(rootNode, lang, source, filePath) {
12266
+ const symbols = [];
12267
+ const topLevelTypes = TOP_LEVEL_TYPES[lang] ?? {};
12268
+ for (const child of rootNode.children) {
12269
+ if (child.type === "export_statement") {
12270
+ symbols.push(processExportStatement(child, topLevelTypes, lang, source, filePath));
12271
+ continue;
12272
+ }
12273
+ const kind = topLevelTypes[child.type];
12274
+ if (!kind || kind === "import") continue;
12275
+ const sym = nodeToSymbol(child, kind, source, filePath);
12276
+ if (kind === "class") {
12277
+ sym.children = extractMethods(child, lang, source, filePath);
12278
+ }
12279
+ symbols.push(sym);
12280
+ }
12281
+ return symbols;
12282
+ }
12283
+ function buildFailedResult(filePath, lang) {
12284
+ return { file: filePath, language: lang, totalLines: 0, symbols: [], error: "[parse-failed]" };
12285
+ }
12286
+ async function getOutline(filePath) {
12287
+ const lang = detectLanguage(filePath);
12288
+ if (!lang) return buildFailedResult(filePath, "unknown");
12289
+ const result = await parseFile(filePath);
12290
+ if (!result.ok) return buildFailedResult(filePath, lang);
12291
+ const { tree, source } = result.value;
12292
+ const totalLines = source.split("\n").length;
12293
+ const symbols = extractSymbols(tree.rootNode, lang, source, filePath);
12294
+ return { file: filePath, language: lang, totalLines, symbols };
12295
+ }
12296
+ function formatOutline(outline) {
12297
+ if (outline.error) {
12298
+ return `${outline.file} ${outline.error}`;
12299
+ }
12300
+ const lines = [`${outline.file} (${outline.totalLines} lines)`];
12301
+ const last = outline.symbols.length - 1;
12302
+ outline.symbols.forEach((sym, i) => {
12303
+ const prefix = i === last ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
12304
+ lines.push(`${prefix} ${sym.signature} :${sym.line}`);
12305
+ if (sym.children) {
12306
+ const childLast = sym.children.length - 1;
12307
+ sym.children.forEach((child, j) => {
12308
+ const childConnector = i === last ? " " : "\u2502 ";
12309
+ const childPrefix = j === childLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
12310
+ lines.push(`${childConnector}${childPrefix} ${child.signature} :${child.line}`);
12311
+ });
12312
+ }
12313
+ });
12314
+ return lines.join("\n");
12315
+ }
12316
+
12317
+ // src/code-nav/search.ts
12318
+ function buildGlob(directory, fileGlob) {
12319
+ const dir = directory.replaceAll("\\", "/");
12320
+ if (fileGlob) {
12321
+ return `${dir}/**/${fileGlob}`;
12322
+ }
12323
+ const exts = Object.keys(EXTENSION_MAP).map((e) => e.slice(1));
12324
+ return `${dir}/**/*.{${exts.join(",")}}`;
12325
+ }
12326
+ function matchesQuery(name, query) {
12327
+ return name.toLowerCase().includes(query.toLowerCase());
12328
+ }
12329
+ function flattenSymbols(symbols) {
12330
+ const flat = [];
12331
+ for (const sym of symbols) {
12332
+ flat.push(sym);
12333
+ if (sym.children) {
12334
+ flat.push(...sym.children);
12335
+ }
12336
+ }
12337
+ return flat;
12338
+ }
12339
+ async function searchSymbols(query, directory, fileGlob) {
12340
+ const pattern = buildGlob(directory, fileGlob);
12341
+ let files;
12342
+ try {
12343
+ files = await findFiles(pattern, directory);
12344
+ } catch {
12345
+ files = [];
12346
+ }
12347
+ const matches = [];
12348
+ const skipped = [];
12349
+ for (const file of files) {
12350
+ const lang = detectLanguage(file);
12351
+ if (!lang) {
12352
+ skipped.push(file);
12353
+ continue;
12354
+ }
12355
+ const outline = await getOutline(file);
12356
+ if (outline.error) {
12357
+ skipped.push(file);
12358
+ continue;
12359
+ }
12360
+ const allSymbols = flattenSymbols(outline.symbols);
12361
+ for (const sym of allSymbols) {
12362
+ if (matchesQuery(sym.name, query)) {
12363
+ matches.push({
12364
+ symbol: sym,
12365
+ context: sym.signature
12366
+ });
12367
+ }
12368
+ }
12369
+ }
12370
+ return { query, matches, skipped };
12371
+ }
12372
+
12373
+ // src/code-nav/unfold.ts
12374
+ function findSymbolInList(symbols, name) {
12375
+ for (const sym of symbols) {
12376
+ if (sym.name === name) return sym;
12377
+ if (sym.children) {
12378
+ const found = findSymbolInList(sym.children, name);
12379
+ if (found) return found;
12380
+ }
12381
+ }
12382
+ return null;
12383
+ }
12384
+ function extractLines(source, startLine, endLine) {
12385
+ const lines = source.split("\n");
12386
+ const start = Math.max(0, startLine - 1);
12387
+ const end = Math.min(lines.length, endLine);
12388
+ return lines.slice(start, end).join("\n");
12389
+ }
12390
+ function buildFallbackResult(filePath, symbolName, content, language) {
12391
+ const totalLines = content ? content.split("\n").length : 0;
12392
+ return {
12393
+ file: filePath,
12394
+ symbolName,
12395
+ startLine: content ? 1 : 0,
12396
+ endLine: totalLines,
12397
+ content,
12398
+ language,
12399
+ fallback: true,
12400
+ warning: "[fallback: raw content]"
12401
+ };
12402
+ }
12403
+ async function readContentSafe(filePath) {
12404
+ const result = await readFileContent(filePath);
12405
+ return result.ok ? result.value : "";
12406
+ }
12407
+ async function unfoldSymbol(filePath, symbolName) {
12408
+ const lang = detectLanguage(filePath);
12409
+ if (!lang) {
12410
+ const content2 = await readContentSafe(filePath);
12411
+ return buildFallbackResult(filePath, symbolName, content2, "unknown");
12412
+ }
12413
+ const outline = await getOutline(filePath);
12414
+ if (outline.error) {
12415
+ const content2 = await readContentSafe(filePath);
12416
+ return buildFallbackResult(filePath, symbolName, content2, lang);
12417
+ }
12418
+ const symbol = findSymbolInList(outline.symbols, symbolName);
12419
+ if (!symbol) {
12420
+ const content2 = await readContentSafe(filePath);
12421
+ return buildFallbackResult(filePath, symbolName, content2, lang);
12422
+ }
12423
+ const parseResult = await parseFile(filePath);
12424
+ if (!parseResult.ok) {
12425
+ const content2 = await readContentSafe(filePath);
12426
+ return {
12427
+ ...buildFallbackResult(
12428
+ filePath,
12429
+ symbolName,
12430
+ extractLines(content2, symbol.line, symbol.endLine),
12431
+ lang
12432
+ ),
12433
+ startLine: symbol.line,
12434
+ endLine: symbol.endLine
12435
+ };
12436
+ }
12437
+ const content = extractLines(parseResult.value.source, symbol.line, symbol.endLine);
12438
+ return {
12439
+ file: filePath,
12440
+ symbolName,
12441
+ startLine: symbol.line,
12442
+ endLine: symbol.endLine,
12443
+ content,
12444
+ language: lang,
12445
+ fallback: false
12446
+ };
12447
+ }
12448
+ async function unfoldRange(filePath, startLine, endLine) {
12449
+ const lang = detectLanguage(filePath) ?? "unknown";
12450
+ const contentResult = await readFileContent(filePath);
12451
+ if (!contentResult.ok) {
12452
+ return {
12453
+ file: filePath,
12454
+ startLine: 0,
12455
+ endLine: 0,
12456
+ content: "",
12457
+ language: lang,
12458
+ fallback: true,
12459
+ warning: "[fallback: raw content]"
12460
+ };
12461
+ }
12462
+ const totalLines = contentResult.value.split("\n").length;
12463
+ const clampedEnd = Math.min(endLine, totalLines);
12464
+ const content = extractLines(contentResult.value, startLine, clampedEnd);
12465
+ return {
12466
+ file: filePath,
12467
+ startLine,
12468
+ endLine: clampedEnd,
12469
+ content,
12470
+ language: lang,
12471
+ fallback: false
12472
+ };
12473
+ }
12474
+
11353
12475
  // src/index.ts
11354
- var VERSION = "0.14.0";
12476
+ var VERSION = "0.15.0";
11355
12477
  // Annotate the CommonJS export names for ESM import in node:
11356
12478
  0 && (module.exports = {
11357
12479
  AGENT_DESCRIPTORS,
@@ -11386,6 +12508,7 @@ var VERSION = "0.14.0";
11386
12508
  DEFAULT_STATE,
11387
12509
  DEFAULT_STREAM_INDEX,
11388
12510
  DepDepthCollector,
12511
+ EXTENSION_MAP,
11389
12512
  EmitInteractionInputSchema,
11390
12513
  EntropyAnalyzer,
11391
12514
  EntropyConfigSchema,
@@ -11420,6 +12543,7 @@ var VERSION = "0.14.0";
11420
12543
  SharableForbiddenImportSchema,
11421
12544
  SharableLayerSchema,
11422
12545
  SharableSecurityRulesSchema,
12546
+ SkillEventSchema,
11423
12547
  StreamIndexSchema,
11424
12548
  StreamInfoSchema,
11425
12549
  ThresholdConfigSchema,
@@ -11428,6 +12552,7 @@ var VERSION = "0.14.0";
11428
12552
  VERSION,
11429
12553
  ViolationSchema,
11430
12554
  addProvenance,
12555
+ agentConfigRules,
11431
12556
  analyzeDiff,
11432
12557
  analyzeLearningPatterns,
11433
12558
  appendFailure,
@@ -11435,6 +12560,7 @@ var VERSION = "0.14.0";
11435
12560
  appendSessionEntry,
11436
12561
  applyFixes,
11437
12562
  applyHotspotDowngrade,
12563
+ applySyncChanges,
11438
12564
  archMatchers,
11439
12565
  archModule,
11440
12566
  architecture,
@@ -11449,12 +12575,14 @@ var VERSION = "0.14.0";
11449
12575
  checkEligibility,
11450
12576
  checkEvidenceCoverage,
11451
12577
  classifyFinding,
12578
+ clearEventHashCache,
11452
12579
  clearFailuresCache,
11453
12580
  clearLearningsCache,
11454
12581
  configureFeedback,
11455
12582
  constraintRuleId,
11456
12583
  contextBudget,
11457
12584
  contextFilter,
12585
+ countLearningEntries,
11458
12586
  createBoundaryValidator,
11459
12587
  createCommentedCodeFixes,
11460
12588
  createError,
@@ -11478,27 +12606,34 @@ var VERSION = "0.14.0";
11478
12606
  detectCouplingViolations,
11479
12607
  detectDeadCode,
11480
12608
  detectDocDrift,
12609
+ detectLanguage,
11481
12610
  detectPatternViolations,
11482
12611
  detectSizeBudgetViolations,
11483
12612
  detectStack,
11484
12613
  detectStaleConstraints,
11485
12614
  determineAssessment,
11486
12615
  diff,
12616
+ emitEvent,
11487
12617
  executeWorkflow,
11488
12618
  expressRules,
11489
12619
  extractBundle,
12620
+ extractIndexEntry,
11490
12621
  extractMarkdownLinks,
11491
12622
  extractSections,
11492
12623
  fanOutReview,
12624
+ formatEventTimeline,
11493
12625
  formatFindingBlock,
11494
12626
  formatGitHubComment,
11495
12627
  formatGitHubSummary,
12628
+ formatOutline,
11496
12629
  formatTerminalOutput,
11497
12630
  generateAgentsMap,
11498
12631
  generateSuggestions,
11499
12632
  getActionEmitter,
11500
12633
  getExitCode,
11501
12634
  getFeedbackConfig,
12635
+ getOutline,
12636
+ getParser,
11502
12637
  getPhaseCategories,
11503
12638
  getStreamForBranch,
11504
12639
  getUpdateNotification,
@@ -11509,24 +12644,30 @@ var VERSION = "0.14.0";
11509
12644
  listActiveSessions,
11510
12645
  listStreams,
11511
12646
  loadBudgetedLearnings,
12647
+ loadEvents,
11512
12648
  loadFailures,
11513
12649
  loadHandoff,
12650
+ loadIndexEntries,
11514
12651
  loadRelevantLearnings,
11515
12652
  loadSessionSummary,
11516
12653
  loadState,
11517
12654
  loadStreamIndex,
11518
12655
  logAgentAction,
12656
+ mcpRules,
11519
12657
  migrateToStreams,
11520
12658
  networkRules,
11521
12659
  nodeRules,
11522
12660
  parseDateFromEntry,
11523
12661
  parseDiff,
12662
+ parseFile,
12663
+ parseFrontmatter,
11524
12664
  parseManifest,
11525
12665
  parseRoadmap,
11526
12666
  parseSecurityConfig,
11527
12667
  parseSize,
11528
12668
  pathTraversalRules,
11529
12669
  previewFix,
12670
+ promoteSessionLearnings,
11530
12671
  pruneLearnings,
11531
12672
  reactRules,
11532
12673
  readCheckState,
@@ -11538,6 +12679,7 @@ var VERSION = "0.14.0";
11538
12679
  requestMultiplePeerReviews,
11539
12680
  requestPeerReview,
11540
12681
  resetFeedbackConfig,
12682
+ resetParserCache,
11541
12683
  resolveFileToLayer,
11542
12684
  resolveModelTier,
11543
12685
  resolveRuleSeverity,
@@ -11559,6 +12701,7 @@ var VERSION = "0.14.0";
11559
12701
  saveState,
11560
12702
  saveStreamIndex,
11561
12703
  scopeContext,
12704
+ searchSymbols,
11562
12705
  secretRules,
11563
12706
  serializeRoadmap,
11564
12707
  setActiveStream,
@@ -11569,6 +12712,8 @@ var VERSION = "0.14.0";
11569
12712
  tagUncitedFindings,
11570
12713
  touchStream,
11571
12714
  trackAction,
12715
+ unfoldRange,
12716
+ unfoldSymbol,
11572
12717
  updateSessionEntryStatus,
11573
12718
  updateSessionIndex,
11574
12719
  validateAgentsMap,