@outcomeeng/spx 0.6.19 → 0.6.20
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 +14 -0
- package/dist/cli.js +56 -30
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Developer CLI for code validation and session management.
|
|
4
4
|
|
|
5
|
+
Current release: 0.6.20
|
|
6
|
+
|
|
5
7
|
## What is spx?
|
|
6
8
|
|
|
7
9
|
`spx` is a command-line interface (CLI) tool that provides code validation and session management for projects that implement the spec-as-source methodology named [Outcome Engineering](https://outcome.engineering). The `spx` CLI works hand-in-hand with the **Claude Code** and **Codex** [plugin marketplace for Outcome Engineering](https://github.com/outcomeeng/plugins).
|
|
@@ -103,6 +105,18 @@ Sessions are stored in `.spx/sessions/` with priority-based ordering (high > med
|
|
|
103
105
|
|
|
104
106
|
The `spx spec` CLI provides deterministic inspection and evidence-projection commands such as `status`, `next`, and `context`. Spec authoring and tree-management workflows live in the **spec-tree** Claude Code plugin, available at [`outcomeeng/plugins`](https://github.com/outcomeeng/plugins). The plugin provides skills for understanding, authoring, decomposing, contextualizing, testing, refactoring, and aligning specification trees.
|
|
105
107
|
|
|
108
|
+
### Release Preparation
|
|
109
|
+
|
|
110
|
+
Prepare release artifacts after updating the package version:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Generate release notes from product history
|
|
114
|
+
spx release notes
|
|
115
|
+
|
|
116
|
+
# Update configured release documentation for the current package version
|
|
117
|
+
spx release docs sync
|
|
118
|
+
```
|
|
119
|
+
|
|
106
120
|
## Development
|
|
107
121
|
|
|
108
122
|
### Setup
|
package/dist/cli.js
CHANGED
|
@@ -10055,12 +10055,17 @@ var AGENT_RUN_TOOLS = {
|
|
|
10055
10055
|
WRITE: "Write",
|
|
10056
10056
|
EDIT: "Edit"
|
|
10057
10057
|
};
|
|
10058
|
+
var AGENT_FILE_TOOLS = [
|
|
10059
|
+
AGENT_RUN_TOOLS.READ,
|
|
10060
|
+
AGENT_RUN_TOOLS.WRITE,
|
|
10061
|
+
AGENT_RUN_TOOLS.EDIT
|
|
10062
|
+
];
|
|
10058
10063
|
var AGENT_TOOL_PERMISSION_BEHAVIOR = {
|
|
10059
10064
|
ALLOW: "allow",
|
|
10060
10065
|
DENY: "deny"
|
|
10061
10066
|
};
|
|
10062
10067
|
function authorizeAgentFileToolPath(workingDirectory, tool, filePath) {
|
|
10063
|
-
return (tool
|
|
10068
|
+
return AGENT_FILE_TOOLS.includes(tool) && filePath.length > 0 && isPathContained(workingDirectory, filePath) ? AGENT_TOOL_PERMISSION_BEHAVIOR.ALLOW : AGENT_TOOL_PERMISSION_BEHAVIOR.DENY;
|
|
10064
10069
|
}
|
|
10065
10070
|
var AGENT_PERMISSION_MODES = {
|
|
10066
10071
|
DONT_ASK: "dontAsk"
|
|
@@ -10068,6 +10073,7 @@ var AGENT_PERMISSION_MODES = {
|
|
|
10068
10073
|
|
|
10069
10074
|
// src/agent/claude-agent-runner.ts
|
|
10070
10075
|
var AGENT_FILE_TOOL_PATH_INPUT_FIELD = "file_path";
|
|
10076
|
+
var AGENT_PRE_TOOL_USE_HOOK_EVENT = "PreToolUse";
|
|
10071
10077
|
var AGENT_FILE_TOOL_PERMISSION_DENIED_MESSAGE = "Agent file tool target is outside its working directory";
|
|
10072
10078
|
var ClaudeAgentRunner = class {
|
|
10073
10079
|
async run(request) {
|
|
@@ -10096,41 +10102,54 @@ function createAgentRunOptions(request) {
|
|
|
10096
10102
|
cwd: request.workingDirectory,
|
|
10097
10103
|
settingSources: [],
|
|
10098
10104
|
tools: [...request.tools],
|
|
10099
|
-
allowedTools:
|
|
10100
|
-
|
|
10105
|
+
allowedTools: [...request.allowedTools],
|
|
10106
|
+
hooks: {
|
|
10107
|
+
PreToolUse: [{ hooks: [createAgentFileToolPermissionHook(request)] }]
|
|
10108
|
+
},
|
|
10101
10109
|
permissionMode: request.permissionMode,
|
|
10102
10110
|
maxTurns: request.maxTurns
|
|
10103
10111
|
};
|
|
10104
10112
|
}
|
|
10105
|
-
function
|
|
10106
|
-
return
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
return async (toolName, input) => {
|
|
10110
|
-
if (!isAgentRunTool(toolName) || !request.allowedTools.includes(toolName)) {
|
|
10111
|
-
return deniedAgentToolPermission();
|
|
10113
|
+
function createAgentFileToolPermissionHook(request) {
|
|
10114
|
+
return async (input) => {
|
|
10115
|
+
if (input.hook_event_name !== AGENT_PRE_TOOL_USE_HOOK_EVENT || !isAgentRunTool(input.tool_name) || !request.allowedTools.includes(input.tool_name) || !isRecord8(input.tool_input)) {
|
|
10116
|
+
return deniedAgentFileToolPermission();
|
|
10112
10117
|
}
|
|
10113
|
-
if (!
|
|
10114
|
-
return
|
|
10118
|
+
if (!isAgentFileTool(input.tool_name)) {
|
|
10119
|
+
return allowedAgentFileToolPermission();
|
|
10115
10120
|
}
|
|
10116
|
-
const filePath = input[AGENT_FILE_TOOL_PATH_INPUT_FIELD];
|
|
10117
|
-
if (typeof filePath !== "string" || authorizeAgentFileToolPath(request.workingDirectory,
|
|
10118
|
-
return
|
|
10121
|
+
const filePath = input.tool_input[AGENT_FILE_TOOL_PATH_INPUT_FIELD];
|
|
10122
|
+
if (typeof filePath !== "string" || authorizeAgentFileToolPath(request.workingDirectory, input.tool_name, filePath) === AGENT_TOOL_PERMISSION_BEHAVIOR.DENY) {
|
|
10123
|
+
return deniedAgentFileToolPermission();
|
|
10119
10124
|
}
|
|
10120
|
-
return
|
|
10125
|
+
return allowedAgentFileToolPermission();
|
|
10121
10126
|
};
|
|
10122
10127
|
}
|
|
10123
|
-
function
|
|
10128
|
+
function allowedAgentFileToolPermission() {
|
|
10124
10129
|
return {
|
|
10125
|
-
|
|
10126
|
-
|
|
10130
|
+
hookSpecificOutput: {
|
|
10131
|
+
hookEventName: AGENT_PRE_TOOL_USE_HOOK_EVENT,
|
|
10132
|
+
permissionDecision: AGENT_TOOL_PERMISSION_BEHAVIOR.ALLOW
|
|
10133
|
+
}
|
|
10127
10134
|
};
|
|
10128
10135
|
}
|
|
10136
|
+
function deniedAgentFileToolPermission() {
|
|
10137
|
+
return {
|
|
10138
|
+
hookSpecificOutput: {
|
|
10139
|
+
hookEventName: AGENT_PRE_TOOL_USE_HOOK_EVENT,
|
|
10140
|
+
permissionDecision: AGENT_TOOL_PERMISSION_BEHAVIOR.DENY,
|
|
10141
|
+
permissionDecisionReason: AGENT_FILE_TOOL_PERMISSION_DENIED_MESSAGE
|
|
10142
|
+
}
|
|
10143
|
+
};
|
|
10144
|
+
}
|
|
10145
|
+
function isRecord8(input) {
|
|
10146
|
+
return typeof input === "object" && input !== null;
|
|
10147
|
+
}
|
|
10129
10148
|
function isAgentRunTool(toolName) {
|
|
10130
10149
|
return Object.values(AGENT_RUN_TOOLS).some((tool) => tool === toolName);
|
|
10131
10150
|
}
|
|
10132
|
-
function
|
|
10133
|
-
return
|
|
10151
|
+
function isAgentFileTool(tool) {
|
|
10152
|
+
return AGENT_FILE_TOOLS.includes(tool);
|
|
10134
10153
|
}
|
|
10135
10154
|
async function runClaudeQuery(prompt, options) {
|
|
10136
10155
|
let result;
|
|
@@ -10302,7 +10321,9 @@ function toComponent(value) {
|
|
|
10302
10321
|
}
|
|
10303
10322
|
|
|
10304
10323
|
// src/domains/release/documentation-sync.ts
|
|
10305
|
-
var DOCUMENTATION_SYNC_PROMPT_INSTRUCTION = "
|
|
10324
|
+
var DOCUMENTATION_SYNC_PROMPT_INSTRUCTION = "Edit every staged documentation file so its version references and behavior descriptions match the supplied release data.";
|
|
10325
|
+
var DOCUMENTATION_SYNC_RELEASE_VERSION_INSTRUCTION = "The exact released version every staged document must contain is";
|
|
10326
|
+
var DOCUMENTATION_SYNC_VERSIONLESS_INSTRUCTION = "Replace every standalone previous product release-version reference. When a staged document has no such reference, add a concise current-release reference using the exact released version above.";
|
|
10306
10327
|
var DOCUMENTATION_SYNC_PROMPT_DATA_BLOCK_OPEN = "<documentation-sync-input>";
|
|
10307
10328
|
var DOCUMENTATION_SYNC_PROMPT_DATA_BLOCK_CLOSE = "</documentation-sync-input>";
|
|
10308
10329
|
var DOCUMENTATION_SYNC_AGENT_TOOLS = [
|
|
@@ -10315,11 +10336,15 @@ var DOCUMENTATION_SYNC_AGENT_MAX_TURNS = 10;
|
|
|
10315
10336
|
var DOCUMENTATION_SYNC_AUDIT_MAX_TURNS = 3;
|
|
10316
10337
|
var DOCUMENTATION_SYNC_AUDIT_APPROVED = "APPROVED";
|
|
10317
10338
|
var DOCUMENTATION_SYNC_AUDIT_REJECTED = "REJECTED";
|
|
10339
|
+
var DOCUMENTATION_SYNC_AUDIT_VERSIONLESS_INSTRUCTION = "When an original document has no previous-release reference, adding a concise current-release reference using the exact released version is a supported release update.";
|
|
10318
10340
|
var REGEXP_SPECIAL_CHARACTER_PATTERN = /[.*+?^${}()|[\]\\]/gu;
|
|
10319
10341
|
var REGEXP_ESCAPE_REPLACEMENT = String.raw`\$&`;
|
|
10320
10342
|
var VERSION_REFERENCE_NON_WHITESPACE_PATTERN = String.raw`\S`;
|
|
10321
10343
|
function buildDocumentationSyncPrompt(input) {
|
|
10344
|
+
const encodedVersion = encodeReleasePromptData(input.releaseData.version).slice(1, -1);
|
|
10322
10345
|
return `${DOCUMENTATION_SYNC_PROMPT_INSTRUCTION}
|
|
10346
|
+
${DOCUMENTATION_SYNC_RELEASE_VERSION_INSTRUCTION} ${encodedVersion}.
|
|
10347
|
+
${DOCUMENTATION_SYNC_VERSIONLESS_INSTRUCTION}
|
|
10323
10348
|
|
|
10324
10349
|
${DOCUMENTATION_SYNC_PROMPT_DATA_BLOCK_OPEN}
|
|
10325
10350
|
${encodeReleasePromptData(input)}
|
|
@@ -10407,6 +10432,7 @@ function containsReleaseVersionReference(content, version2) {
|
|
|
10407
10432
|
function buildDocumentationFaithfulnessAuditPrompt(input) {
|
|
10408
10433
|
return [
|
|
10409
10434
|
"Audit whether every original-to-updated documentation transformation faithfully applies the supplied release data, including updating each previous-release reference rather than deleting it.",
|
|
10435
|
+
DOCUMENTATION_SYNC_AUDIT_VERSIONLESS_INSTRUCTION,
|
|
10410
10436
|
`Return exactly ${DOCUMENTATION_SYNC_AUDIT_APPROVED} when every changed claim is supported and every previous-release reference remains represented by the released version.`,
|
|
10411
10437
|
`Return ${DOCUMENTATION_SYNC_AUDIT_REJECTED} followed by a concise reason for any unsupported claim, deleted previous-release reference, or omitted release update.`,
|
|
10412
10438
|
DOCUMENTATION_SYNC_PROMPT_DATA_BLOCK_OPEN,
|
|
@@ -16084,7 +16110,7 @@ async function readTestRunStatePath(runFilePath, fs4) {
|
|
|
16084
16110
|
return { ok: true, value: validated.value };
|
|
16085
16111
|
}
|
|
16086
16112
|
function validateTestRunState(value) {
|
|
16087
|
-
if (!
|
|
16113
|
+
if (!isRecord9(value)) return { ok: false, error: "testing run state must be an object" };
|
|
16088
16114
|
const branchName = readString2(value, TEST_RUN_STATE_FIELDS.BRANCH_NAME);
|
|
16089
16115
|
if (!branchName.ok) return branchName;
|
|
16090
16116
|
const headSha = readString2(value, TEST_RUN_STATE_FIELDS.HEAD_SHA);
|
|
@@ -16127,7 +16153,7 @@ function readRunnerOutcomes(raw) {
|
|
|
16127
16153
|
}
|
|
16128
16154
|
const outcomes = [];
|
|
16129
16155
|
for (const entry of raw) {
|
|
16130
|
-
if (!
|
|
16156
|
+
if (!isRecord9(entry)) {
|
|
16131
16157
|
return { ok: false, error: `${TEST_RUN_STATE_FIELDS.RUNNER_OUTCOMES} entries must be objects` };
|
|
16132
16158
|
}
|
|
16133
16159
|
const runnerId = readString2(entry, TEST_RUNNER_OUTCOME_FIELDS.RUNNER_ID);
|
|
@@ -16148,7 +16174,7 @@ function readProductInputDigests(raw) {
|
|
|
16148
16174
|
}
|
|
16149
16175
|
const digests = [];
|
|
16150
16176
|
for (const entry of raw) {
|
|
16151
|
-
if (!
|
|
16177
|
+
if (!isRecord9(entry)) {
|
|
16152
16178
|
return { ok: false, error: `${TEST_RUN_STATE_FIELDS.PRODUCT_INPUT_DIGESTS} entries must be objects` };
|
|
16153
16179
|
}
|
|
16154
16180
|
const descriptorId = readString2(entry, PRODUCT_INPUT_DIGEST_FIELDS.DESCRIPTOR_ID);
|
|
@@ -16210,7 +16236,7 @@ function testingWriteError(error) {
|
|
|
16210
16236
|
function withDomainErrorDetail(domainError, detail) {
|
|
16211
16237
|
return detail === void 0 ? domainError : `${domainError}: ${detail}`;
|
|
16212
16238
|
}
|
|
16213
|
-
function
|
|
16239
|
+
function isRecord9(value) {
|
|
16214
16240
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16215
16241
|
}
|
|
16216
16242
|
function sha256Hex2(value) {
|
|
@@ -17349,7 +17375,7 @@ function importSpecifiers(sourceText, testPath) {
|
|
|
17349
17375
|
}
|
|
17350
17376
|
return specifiers;
|
|
17351
17377
|
}
|
|
17352
|
-
function
|
|
17378
|
+
function isRecord10(value) {
|
|
17353
17379
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17354
17380
|
}
|
|
17355
17381
|
function hasErrorCode3(error, code) {
|
|
@@ -17384,11 +17410,11 @@ function pathMappingFromTsconfigPath(pathAlias, targets) {
|
|
|
17384
17410
|
};
|
|
17385
17411
|
}
|
|
17386
17412
|
function pathMappingsFromTsconfig(config) {
|
|
17387
|
-
if (!
|
|
17413
|
+
if (!isRecord10(config)) return [];
|
|
17388
17414
|
const compilerOptions = config[TSCONFIG_COMPILER_OPTIONS_KEY];
|
|
17389
|
-
if (!
|
|
17415
|
+
if (!isRecord10(compilerOptions)) return [];
|
|
17390
17416
|
const paths = compilerOptions[TSCONFIG_PATHS_KEY];
|
|
17391
|
-
if (!
|
|
17417
|
+
if (!isRecord10(paths)) return [];
|
|
17392
17418
|
return Object.entries(paths).flatMap(([pathAlias, targets]) => {
|
|
17393
17419
|
const mapping = pathMappingFromTsconfigPath(pathAlias, targets);
|
|
17394
17420
|
return mapping === null ? [] : [mapping];
|