@outcomeeng/spx 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -11,13 +11,14 @@ Developer CLI for code validation and session management.
11
11
  - **Unified validation**: Run ESLint, TypeScript, and circular dependency checks through a single command
12
12
  - **Session management**: Queue, claim, and hand off work between agents
13
13
  - **Multiple formats**: Text, JSON output for CI and automation
14
+ - **Secure publishing**: OIDC Trusted Publishing with Sigstore provenance via GitHub Actions
14
15
 
15
16
  All commands are domain-scoped (e.g., `spx validation`, `spx session`) and support `--quiet` and `--json` flags for CI and automation.
16
17
 
17
18
  ## Installation
18
19
 
19
20
  ```bash
20
- pnpm add -g @outcomeeng/spx
21
+ npm install -g @outcomeeng/spx
21
22
  ```
22
23
 
23
24
  ### From Source
@@ -62,20 +63,12 @@ priority: high
62
63
  ---
63
64
  # Implement feature X
64
65
  EOF
65
- # Output:
66
- # Created handoff session <HANDOFF_ID>2026-01-15_08-30-00</HANDOFF_ID>
67
- # <SESSION_FILE>/path/to/.spx/sessions/todo/2026-01-15_08-30-00.md</SESSION_FILE>
68
-
69
- # Or create empty session and edit the file directly
70
- spx session handoff
71
- # Then edit the <SESSION_FILE> path returned
72
66
 
73
67
  # List all sessions
74
68
  spx session list
75
69
 
76
70
  # Claim the highest priority session
77
71
  spx session pickup --auto
78
- # Output: Claimed session <PICKUP_ID>2026-01-15_08-30-00</PICKUP_ID>
79
72
 
80
73
  # Release session back to queue
81
74
  spx session release
@@ -87,10 +80,14 @@ spx session show <session-id>
87
80
  spx session delete <session-id>
88
81
  ```
89
82
 
90
- Sessions are stored in `.spx/sessions/` with priority-based ordering (high medium low) and FIFO within the same priority. Commands output parseable `<PICKUP_ID>`, `<HANDOFF_ID>`, and `<SESSION_FILE>` tags for automation.
83
+ Sessions are stored in `.spx/sessions/` with priority-based ordering (high > medium > low) and FIFO within the same priority. Commands output parseable `<PICKUP_ID>`, `<HANDOFF_ID>`, and `<SESSION_FILE>` tags for automation.
91
84
 
92
85
  See [Session Recipes](docs/how-to/session/common-tasks.md) for detailed usage patterns.
93
86
 
87
+ ### Spec Management (deprecated)
88
+
89
+ The `spx spec` and `spx spx` CLI domains are **deprecated**. Spec tree management has moved to the **spec-tree** Claude Code plugin, available at [`outcomeeng/claude/plugins/spec-tree`](https://github.com/outcomeeng/claude). The plugin provides skills for understanding, authoring, decomposing, contextualizing, testing, refactoring, and aligning specification trees.
90
+
94
91
  ## Development
95
92
 
96
93
  ### Setup
@@ -128,21 +125,39 @@ pnpm run knip # Unused code detection
128
125
 
129
126
  The `pnpm run` scripts use `node bin/spx.js` internally, so they work without a global link. Once linked, you can also use `spx validation all` etc. directly.
130
127
 
128
+ ## CI/CD
129
+
130
+ The project uses GitHub Actions for continuous integration and publishing:
131
+
132
+ - **CI** (`ci.yml`) — Runs validate, test, and build on Node 22 and 24 for every push to `main` and every pull request. Includes dependency review to block PRs introducing vulnerable dependencies.
133
+ - **Publish** (`publish.yml`) — Triggered by `v*` tags. Uses OIDC Trusted Publishing (no stored npm tokens) with Sigstore provenance attestation. Requires manual approval via the `npm-publish` GitHub Environment.
134
+ - **Scorecard** (`scorecard.yml`) — Weekly OpenSSF Scorecard assessment, results published to the GitHub Security tab.
135
+
136
+ ### Publishing a Release
137
+
138
+ 1. Bump the version in `package.json`
139
+ 2. Commit and tag: `git tag vX.Y.Z`
140
+ 3. Push: `git push origin main && git push origin vX.Y.Z`
141
+ 4. Approve the deployment in the GitHub Actions `npm-publish` environment
142
+ 5. The package is published with provenance — verify with `npm audit signatures`
143
+
131
144
  ## Technical Stack
132
145
 
133
- - **TypeScript** - Type-safe implementation
134
- - **Commander.js** - CLI framework
135
- - **Vitest** - Testing framework
136
- - **tsup** - Build tool
137
- - **ESLint 9** - Linting with flat config
146
+ - **TypeScript** Type-safe implementation (ESM)
147
+ - **Commander.js** CLI framework
148
+ - **Vitest** Testing framework
149
+ - **tsup** Build tool (esbuild-based)
150
+ - **ESLint 9** Linting with flat config
151
+ - **GitHub Actions** — CI/CD with OIDC Trusted Publishing
138
152
 
139
153
  ## Architecture
140
154
 
141
155
  ```
142
156
  src/
143
157
  ├── commands/ # CLI command implementations
144
- │ ├── validation/ # spx validation subcommands
145
- │ └── session/ # spx session subcommands
158
+ │ ├── session/ # spx session subcommands
159
+ │ └── validation/ # spx validation subcommands
160
+ ├── domains/ # Domain routers
146
161
  ├── validation/ # Lint, typecheck, circular dep logic
147
162
  ├── session/ # Session lifecycle and storage
148
163
  ├── config/ # Configuration loading
@@ -151,6 +166,7 @@ src/
151
166
  ├── status/ # Status state machine
152
167
  ├── reporter/ # Output formatting
153
168
  ├── tree/ # Hierarchical tree building
169
+ ├── precommit/ # Pre-commit hook orchestration
154
170
  └── lib/ # Shared utilities
155
171
  ```
156
172
 
@@ -57,4 +57,4 @@ export {
57
57
  LEAF_KIND,
58
58
  WORK_ITEM_STATUSES
59
59
  };
60
- //# sourceMappingURL=chunk-5L7CHFBC.js.map
60
+ //# sourceMappingURL=chunk-BQLLI7GS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scanner/validation.ts","../src/scanner/patterns.ts","../src/types.ts"],"sourcesContent":["/**\n * Validation functions for BSP (Behavior, Structure, Property) numbers\n */\n\n/**\n * BSP number range constants\n */\nexport const MIN_BSP_NUMBER = 10;\nexport const MAX_BSP_NUMBER = 99;\n\n/**\n * Check if a number is a valid BSP number\n *\n * @param n - Number to validate\n * @returns true if n is in range [10, 99], false otherwise\n *\n * @example\n * ```typescript\n * isValidBSPNumber(20) // true\n * isValidBSPNumber(9) // false\n * isValidBSPNumber(100) // false\n * ```\n */\nexport function isValidBSPNumber(n: number): boolean {\n return n >= MIN_BSP_NUMBER && n <= MAX_BSP_NUMBER;\n}\n\n/**\n * Validate a BSP number and throw if invalid\n *\n * @param n - Number to validate\n * @returns The validated number\n * @throws Error if the number is outside the valid range [10, 99]\n *\n * @example\n * ```typescript\n * validateBSPNumber(20) // returns 20\n * validateBSPNumber(5) // throws Error: \"BSP number must be between 10 and 99, got 5\"\n * ```\n */\nexport function validateBSPNumber(n: number): number {\n if (!isValidBSPNumber(n)) {\n throw new Error(\n `BSP number must be between ${MIN_BSP_NUMBER} and ${MAX_BSP_NUMBER}, got ${n}`,\n );\n }\n return n;\n}\n","/**\n * Pattern matching for work item directory names\n */\nimport type { WorkItem, WorkItemKind } from \"../types.js\";\nimport { validateBSPNumber } from \"./validation.js\";\n\n/**\n * Regex pattern for work item directory names\n * Format: {kind}-{number}_{slug}\n * - kind: capability, feature, or story\n * - number: BSP number (10-99)\n * - slug: kebab-case identifier (lowercase, hyphens only)\n */\nconst WORK_ITEM_PATTERN = /^(capability|feature|story)-(\\d+)_([a-z][a-z0-9-]*)$/;\n\n/**\n * Parse a work item directory name into structured data\n *\n * @param dirName - Directory name to parse\n * @returns Parsed work item with kind, number, and slug (path not included)\n * @throws Error if the directory name doesn't match the pattern or BSP number is invalid\n *\n * @example\n * ```typescript\n * parseWorkItemName(\"capability-21_core-cli\")\n * // Returns: { kind: \"capability\", number: 20, slug: \"core-cli\" }\n *\n * parseWorkItemName(\"feature-21_pattern-matching\")\n * // Returns: { kind: \"feature\", number: 21, slug: \"pattern-matching\" }\n * ```\n */\nexport function parseWorkItemName(dirName: string): Omit<WorkItem, \"path\"> {\n const match = WORK_ITEM_PATTERN.exec(dirName);\n\n if (!match) {\n throw new Error(\n `Invalid work item name: \"${dirName}\". Expected format: {kind}-{number}_{slug} `\n + `(e.g., \"capability-21_core-cli\", \"feature-21_pattern-matching\")`,\n );\n }\n\n const kind = match[1] as WorkItemKind;\n const bspNumber = parseInt(match[2], 10);\n const slug = match[3];\n\n // Validate BSP number range using validation module\n validateBSPNumber(bspNumber);\n\n // Capabilities use 0-indexed numbers (directory number - 1)\n // Features and stories use directory number as-is\n const number = kind === \"capability\" ? bspNumber - 1 : bspNumber;\n\n return {\n kind,\n number,\n slug,\n };\n}\n","/**\n * Core type definitions for spx\n */\n\n/**\n * Work item types in the spec hierarchy\n */\nexport type WorkItemKind = \"capability\" | \"feature\" | \"story\";\n\n/**\n * Ordered hierarchy of work item kinds (root to leaf)\n *\n * Used by both production code and tests to derive hierarchy structure.\n * Per ADR-21: Never hardcode kind names - derive from this constant.\n */\nexport const WORK_ITEM_KINDS: readonly WorkItemKind[] = [\n \"capability\",\n \"feature\",\n \"story\",\n] as const;\n\n/**\n * The leaf kind (actionable work items)\n *\n * Derived from WORK_ITEM_KINDS to ensure consistency if hierarchy changes.\n */\nexport const LEAF_KIND: WorkItemKind = WORK_ITEM_KINDS.at(-1)!;\n\n/**\n * Parsed work item structure\n */\nexport interface WorkItem {\n /** The type of work item */\n kind: WorkItemKind;\n /** BSP number (0-indexed for capabilities, as-is for features/stories) */\n number: number;\n /** URL-safe slug identifier */\n slug: string;\n /** Full filesystem path to work item directory */\n path: string;\n}\n\n/**\n * Directory entry from filesystem traversal\n */\nexport interface DirectoryEntry {\n /** Directory name (basename) */\n name: string;\n /** Full absolute path */\n path: string;\n /** Whether this is a directory */\n isDirectory: boolean;\n}\n\n/**\n * Work item status\n */\nexport type WorkItemStatus = \"OPEN\" | \"IN_PROGRESS\" | \"DONE\";\n\n/**\n * Ordered list of work item statuses\n *\n * Used by both production code and tests to derive status values.\n * Per ADR-21: Never hardcode status names - derive from this constant.\n */\nexport const WORK_ITEM_STATUSES: readonly WorkItemStatus[] = [\n \"OPEN\",\n \"IN_PROGRESS\",\n \"DONE\",\n] as const;\n"],"mappings":";AAOO,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AAevB,SAAS,iBAAiB,GAAoB;AACnD,SAAO,KAAK,kBAAkB,KAAK;AACrC;AAeO,SAAS,kBAAkB,GAAmB;AACnD,MAAI,CAAC,iBAAiB,CAAC,GAAG;AACxB,UAAM,IAAI;AAAA,MACR,8BAA8B,cAAc,QAAQ,cAAc,SAAS,CAAC;AAAA,IAC9E;AAAA,EACF;AACA,SAAO;AACT;;;AClCA,IAAM,oBAAoB;AAkBnB,SAAS,kBAAkB,SAAyC;AACzE,QAAM,QAAQ,kBAAkB,KAAK,OAAO;AAE5C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,4BAA4B,OAAO;AAAA,IAErC;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,CAAC;AACpB,QAAM,YAAY,SAAS,MAAM,CAAC,GAAG,EAAE;AACvC,QAAM,OAAO,MAAM,CAAC;AAGpB,oBAAkB,SAAS;AAI3B,QAAM,SAAS,SAAS,eAAe,YAAY,IAAI;AAEvD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC1CO,IAAM,kBAA2C;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AACF;AAOO,IAAM,YAA0B,gBAAgB,GAAG,EAAE;AAuCrD,IAAM,qBAAgD;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AACF;","names":[]}