@outcomeeng/spx 0.3.0 → 0.3.2

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/cli.js CHANGED
@@ -1707,6 +1707,17 @@ var PRIORITY_ORDER = {
1707
1707
  low: 2
1708
1708
  };
1709
1709
  var DEFAULT_PRIORITY = "medium";
1710
+ var SESSION_FRONT_MATTER = {
1711
+ PRIORITY: "priority",
1712
+ TAGS: "tags",
1713
+ ID: "id",
1714
+ BRANCH: "branch",
1715
+ CREATED_AT: "created_at",
1716
+ AGENT_SESSION_ID: "agent_session_id",
1717
+ WORKING_DIRECTORY: "working_directory",
1718
+ SPECS: "specs",
1719
+ FILES: "files"
1720
+ };
1710
1721
 
1711
1722
  // src/session/list.ts
1712
1723
  var FRONT_MATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n(?:---|\.\.\.)\r?\n?/;
@@ -1729,36 +1740,28 @@ function parseSessionMetadata(content) {
1729
1740
  tags: []
1730
1741
  };
1731
1742
  }
1732
- const priority = isValidPriority(parsed.priority) ? parsed.priority : DEFAULT_PRIORITY;
1733
- let tags = [];
1734
- if (Array.isArray(parsed.tags)) {
1735
- tags = parsed.tags.filter((t) => typeof t === "string");
1736
- }
1737
- const metadata = {
1738
- priority,
1739
- tags
1740
- };
1741
- if (typeof parsed.id === "string") {
1742
- metadata.id = parsed.id;
1743
- }
1744
- if (typeof parsed.branch === "string") {
1745
- metadata.branch = parsed.branch;
1746
- }
1747
- if (typeof parsed.created_at === "string") {
1748
- metadata.createdAt = parsed.created_at;
1749
- }
1750
- if (typeof parsed.working_directory === "string") {
1751
- metadata.workingDirectory = parsed.working_directory;
1752
- }
1753
- if (Array.isArray(parsed.specs)) {
1754
- metadata.specs = parsed.specs.filter(
1755
- (s) => typeof s === "string"
1756
- );
1757
- }
1758
- if (Array.isArray(parsed.files)) {
1759
- metadata.files = parsed.files.filter(
1760
- (f) => typeof f === "string"
1761
- );
1743
+ const rawPriority = parsed[SESSION_FRONT_MATTER.PRIORITY];
1744
+ const priority = isValidPriority(rawPriority) ? rawPriority : DEFAULT_PRIORITY;
1745
+ const rawTags = parsed[SESSION_FRONT_MATTER.TAGS];
1746
+ const tags = Array.isArray(rawTags) ? rawTags.filter((t) => typeof t === "string") : [];
1747
+ const metadata = { priority, tags };
1748
+ const id = parsed[SESSION_FRONT_MATTER.ID];
1749
+ if (typeof id === "string") metadata.id = id;
1750
+ const branch = parsed[SESSION_FRONT_MATTER.BRANCH];
1751
+ if (typeof branch === "string") metadata.branch = branch;
1752
+ const createdAt = parsed[SESSION_FRONT_MATTER.CREATED_AT];
1753
+ if (typeof createdAt === "string") metadata.createdAt = createdAt;
1754
+ const agentSessionId = parsed[SESSION_FRONT_MATTER.AGENT_SESSION_ID];
1755
+ if (typeof agentSessionId === "string") metadata.agentSessionId = agentSessionId;
1756
+ const workingDirectory = parsed[SESSION_FRONT_MATTER.WORKING_DIRECTORY];
1757
+ if (typeof workingDirectory === "string") metadata.workingDirectory = workingDirectory;
1758
+ const specs = parsed[SESSION_FRONT_MATTER.SPECS];
1759
+ if (Array.isArray(specs)) {
1760
+ metadata.specs = specs.filter((s) => typeof s === "string");
1761
+ }
1762
+ const files = parsed[SESSION_FRONT_MATTER.FILES];
1763
+ if (Array.isArray(files)) {
1764
+ metadata.files = files.filter((f) => typeof f === "string");
1762
1765
  }
1763
1766
  return metadata;
1764
1767
  } catch {
@@ -1855,6 +1858,16 @@ import { join as join5, resolve as resolve4 } from "path";
1855
1858
 
1856
1859
  // src/session/create.ts
1857
1860
  var MIN_CONTENT_LENGTH = 1;
1861
+ var FRONT_MATTER_OPEN = /^---\r?\n/;
1862
+ function preFillSessionContent(content, params) {
1863
+ const match = FRONT_MATTER_OPEN.exec(content);
1864
+ if (!match) return content;
1865
+ const lines = [`${SESSION_FRONT_MATTER.CREATED_AT}: "${params.createdAt.toISOString()}"`];
1866
+ if (params.agentSessionId !== void 0) {
1867
+ lines.push(`${SESSION_FRONT_MATTER.AGENT_SESSION_ID}: "${params.agentSessionId}"`);
1868
+ }
1869
+ return match[0] + lines.join("\n") + "\n" + content.slice(match[0].length);
1870
+ }
1858
1871
  function validateSessionContent(content) {
1859
1872
  if (!content || content.trim().length < MIN_CONTENT_LENGTH) {
1860
1873
  return {
@@ -1892,7 +1905,12 @@ ${content}`;
1892
1905
  async function handoffCommand(options) {
1893
1906
  const { config, warning } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
1894
1907
  const sessionId = generateSessionId();
1895
- const fullContent = buildSessionContent(options.content);
1908
+ const baseContent = buildSessionContent(options.content);
1909
+ const agentSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.CODEX_THREAD_ID;
1910
+ const fullContent = preFillSessionContent(baseContent, {
1911
+ createdAt: /* @__PURE__ */ new Date(),
1912
+ agentSessionId
1913
+ });
1896
1914
  const validation = validateSessionContent(fullContent);
1897
1915
  if (!validation.valid) {
1898
1916
  throw new SessionInvalidContentError(validation.error ?? "Unknown validation error");
@@ -4986,6 +5004,8 @@ function formatSummary(options) {
4986
5004
 
4987
5005
  // src/validation/steps/eslint.ts
4988
5006
  import { spawn } from "child_process";
5007
+ import { existsSync as existsSync3 } from "fs";
5008
+ import { join as join15 } from "path";
4989
5009
 
4990
5010
  // src/validation/types.ts
4991
5011
  var VALIDATION_SCOPES = {
@@ -5035,7 +5055,10 @@ async function validateESLint(context, runner = defaultEslintProcessRunner) {
5035
5055
  cacheFile: CACHE_PATHS.ESLINT,
5036
5056
  configFile: eslintConfigFile
5037
5057
  });
5038
- const eslintProcess = runner.spawn("npx", eslintArgs, {
5058
+ const localBin = join15(projectRoot, "node_modules", ".bin", "eslint");
5059
+ const binary = existsSync3(localBin) ? localBin : "npx";
5060
+ const spawnArgs = binary === "npx" ? eslintArgs : eslintArgs.slice(1);
5061
+ const eslintProcess = runner.spawn(binary, spawnArgs, {
5039
5062
  cwd: projectRoot,
5040
5063
  stdio: "inherit"
5041
5064
  });
@@ -5064,6 +5087,8 @@ function validationEnabled(envVarKey, defaults2 = {}) {
5064
5087
 
5065
5088
  // src/validation/steps/knip.ts
5066
5089
  import { spawn as spawn2 } from "child_process";
5090
+ import { existsSync as existsSync4 } from "fs";
5091
+ import { join as join16 } from "path";
5067
5092
  var defaultKnipProcessRunner = { spawn: spawn2 };
5068
5093
  async function validateKnip(typescriptScope, runner = defaultKnipProcessRunner) {
5069
5094
  try {
@@ -5072,7 +5097,9 @@ async function validateKnip(typescriptScope, runner = defaultKnipProcessRunner)
5072
5097
  return { success: true };
5073
5098
  }
5074
5099
  return new Promise((resolve6) => {
5075
- const knipProcess = runner.spawn("npx", ["knip"], {
5100
+ const localBin = join16(process.cwd(), "node_modules", ".bin", "knip");
5101
+ const binary = existsSync4(localBin) ? localBin : "npx";
5102
+ const knipProcess = runner.spawn(binary, binary === "npx" ? ["knip"] : [], {
5076
5103
  cwd: process.cwd(),
5077
5104
  stdio: "pipe"
5078
5105
  });
@@ -5245,8 +5272,8 @@ function formatLoc(loc) {
5245
5272
  }
5246
5273
 
5247
5274
  // src/validation/steps/markdown.ts
5248
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
5249
- import { basename, join as join15 } from "path";
5275
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
5276
+ import { basename, join as join17 } from "path";
5250
5277
  import { main as markdownlintMain } from "markdownlint-cli2";
5251
5278
  import relativeLinksRule from "markdownlint-rule-relative-links";
5252
5279
  var DEFAULT_DIRECTORY_NAMES = ["spx", "docs"];
@@ -5272,11 +5299,11 @@ function buildMarkdownlintConfig(directoryName) {
5272
5299
  };
5273
5300
  }
5274
5301
  function getDefaultDirectories(projectRoot) {
5275
- return DEFAULT_DIRECTORY_NAMES.map((name) => join15(projectRoot, name)).filter((dir) => existsSync3(dir));
5302
+ return DEFAULT_DIRECTORY_NAMES.map((name) => join17(projectRoot, name)).filter((dir) => existsSync5(dir));
5276
5303
  }
5277
5304
  function getExcludeGlobs(spxDir) {
5278
- const excludePath = join15(spxDir, EXCLUDE_FILENAME);
5279
- if (!existsSync3(excludePath)) {
5305
+ const excludePath = join17(spxDir, EXCLUDE_FILENAME);
5306
+ if (!existsSync5(excludePath)) {
5280
5307
  return [];
5281
5308
  }
5282
5309
  const content = readFileSync2(excludePath, "utf-8");
@@ -5339,7 +5366,7 @@ async function validateDirectory(directory, config, projectRoot, ignoreGlobs = [
5339
5366
  if (parsed) {
5340
5367
  errors.push({
5341
5368
  ...parsed,
5342
- file: join15(directory, parsed.file)
5369
+ file: join17(directory, parsed.file)
5343
5370
  });
5344
5371
  }
5345
5372
  }
@@ -5384,16 +5411,16 @@ async function markdownCommand(options) {
5384
5411
 
5385
5412
  // src/validation/steps/typescript.ts
5386
5413
  import { spawn as spawn3 } from "child_process";
5387
- import { existsSync as existsSync4, mkdirSync, rmSync, writeFileSync } from "fs";
5414
+ import { existsSync as existsSync6, mkdirSync, rmSync, writeFileSync } from "fs";
5388
5415
  import { mkdtemp } from "fs/promises";
5389
5416
  import { tmpdir } from "os";
5390
- import { isAbsolute as isAbsolute3, join as join16 } from "path";
5417
+ import { isAbsolute as isAbsolute3, join as join18 } from "path";
5391
5418
  var defaultTypeScriptProcessRunner = { spawn: spawn3 };
5392
5419
  var defaultTypeScriptDeps = {
5393
5420
  mkdtemp,
5394
5421
  writeFileSync,
5395
5422
  rmSync,
5396
- existsSync: existsSync4,
5423
+ existsSync: existsSync6,
5397
5424
  mkdirSync
5398
5425
  };
5399
5426
  function buildTypeScriptArgs(context) {
@@ -5401,19 +5428,19 @@ function buildTypeScriptArgs(context) {
5401
5428
  return scope === VALIDATION_SCOPES.FULL ? ["tsc", "--noEmit"] : ["tsc", "--project", configFile];
5402
5429
  }
5403
5430
  async function createFileSpecificTsconfig(scope, files, deps = defaultTypeScriptDeps) {
5404
- const tempDir = await deps.mkdtemp(join16(tmpdir(), "validate-ts-"));
5405
- const configPath = join16(tempDir, "tsconfig.json");
5431
+ const tempDir = await deps.mkdtemp(join18(tmpdir(), "validate-ts-"));
5432
+ const configPath = join18(tempDir, "tsconfig.json");
5406
5433
  const baseConfigFile = TSCONFIG_FILES[scope];
5407
5434
  const projectRoot = process.cwd();
5408
- const absoluteFiles = files.map((file) => isAbsolute3(file) ? file : join16(projectRoot, file));
5435
+ const absoluteFiles = files.map((file) => isAbsolute3(file) ? file : join18(projectRoot, file));
5409
5436
  const tempConfig = {
5410
- extends: join16(projectRoot, baseConfigFile),
5437
+ extends: join18(projectRoot, baseConfigFile),
5411
5438
  files: absoluteFiles,
5412
5439
  include: [],
5413
5440
  exclude: [],
5414
5441
  compilerOptions: {
5415
5442
  noEmit: true,
5416
- typeRoots: [join16(projectRoot, "node_modules", "@types")],
5443
+ typeRoots: [join18(projectRoot, "node_modules", "@types")],
5417
5444
  types: ["node"]
5418
5445
  }
5419
5446
  };
@@ -5434,7 +5461,10 @@ async function validateTypeScript(scope, typescriptScope, files, runner = defaul
5434
5461
  const { configPath, cleanup } = await createFileSpecificTsconfig(scope, files, deps);
5435
5462
  try {
5436
5463
  return await new Promise((resolve6) => {
5437
- const tscProcess = runner.spawn("npx", ["tsc", "--project", configPath], {
5464
+ const tscBin = join18(process.cwd(), "node_modules", ".bin", "tsc");
5465
+ const tscBinary = existsSync6(tscBin) ? tscBin : "npx";
5466
+ const tscArgs2 = tscBinary === "npx" ? ["tsc", "--project", configPath] : ["--project", configPath];
5467
+ const tscProcess = runner.spawn(tscBinary, tscArgs2, {
5438
5468
  cwd: process.cwd(),
5439
5469
  stdio: "inherit"
5440
5470
  });
@@ -5457,8 +5487,10 @@ async function validateTypeScript(scope, typescriptScope, files, runner = defaul
5457
5487
  return { success: false, error: `Failed to create temporary config: ${errorMessage}` };
5458
5488
  }
5459
5489
  } else {
5460
- tool = "npx";
5461
- tscArgs = buildTypeScriptArgs({ scope, configFile });
5490
+ const tscBin = join18(process.cwd(), "node_modules", ".bin", "tsc");
5491
+ tool = existsSync6(tscBin) ? tscBin : "npx";
5492
+ const rawArgs = buildTypeScriptArgs({ scope, configFile });
5493
+ tscArgs = tool === "npx" ? rawArgs : rawArgs.slice(1);
5462
5494
  }
5463
5495
  return new Promise((resolve6) => {
5464
5496
  const tscProcess = runner.spawn(tool, tscArgs, {