@magnusekdahl/parallix 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/lib/agents/agents.js +737 -838
  2. package/lib/agents/agents.ts +930 -0
  3. package/lib/agents/claude-telemetry.js +149 -154
  4. package/lib/agents/claude-telemetry.ts +233 -0
  5. package/lib/agents/claude.js +121 -131
  6. package/lib/agents/claude.ts +156 -0
  7. package/lib/agents/codex-telemetry.js +176 -164
  8. package/lib/agents/codex-telemetry.ts +205 -0
  9. package/lib/agents/codex.js +168 -201
  10. package/lib/agents/codex.ts +236 -0
  11. package/lib/agents/limit-hit.js +216 -226
  12. package/lib/agents/limit-hit.ts +262 -0
  13. package/lib/agents/mistral-telemetry.js +7 -10
  14. package/lib/agents/mistral-telemetry.ts +44 -0
  15. package/lib/agents/mistral.js +40 -58
  16. package/lib/agents/mistral.ts +84 -0
  17. package/lib/agents/opencode-export.js +126 -127
  18. package/lib/agents/opencode-export.ts +160 -0
  19. package/lib/agents/opencode-telemetry.js +282 -261
  20. package/lib/agents/opencode-telemetry.ts +350 -0
  21. package/lib/agents/opencode.js +263 -305
  22. package/lib/agents/opencode.ts +370 -0
  23. package/lib/agents/stage-telemetry.js +23 -24
  24. package/lib/agents/stage-telemetry.ts +47 -0
  25. package/lib/commands/integrate.js +9 -3
  26. package/lib/core/fmt.js +140 -211
  27. package/lib/core/fmt.ts +189 -0
  28. package/lib/core/git.js +121 -102
  29. package/lib/core/git.ts +146 -0
  30. package/lib/core/gitignore.js +59 -106
  31. package/lib/core/gitignore.ts +103 -0
  32. package/lib/core/mission-utils.js +833 -875
  33. package/lib/core/mission-utils.ts +998 -0
  34. package/lib/core/persistent-data-migration.js +247 -205
  35. package/lib/core/persistent-data-migration.ts +255 -0
  36. package/lib/core/product-config.js +400 -476
  37. package/lib/core/product-config.ts +518 -0
  38. package/lib/core/runtime-matrix.js +41 -87
  39. package/lib/core/runtime-matrix.ts +84 -0
  40. package/lib/core/spawn-tee.js +152 -192
  41. package/lib/core/spawn-tee.ts +202 -0
  42. package/lib/core/state-map.js +92 -92
  43. package/lib/core/state-map.ts +115 -0
  44. package/lib/core/storage.js +101 -129
  45. package/lib/core/storage.ts +182 -0
  46. package/lib/core/verification.js +148 -138
  47. package/lib/core/verification.ts +200 -0
  48. package/package.json +4 -2
  49. package/prompts/draft.md +1 -0
  50. package/prompts/portfolio.md +1 -1
  51. package/px.js +0 -2
@@ -0,0 +1,200 @@
1
+ import { git, run } from './git.js';
2
+ import type { SpawnSyncOptions } from 'node:child_process';
3
+ import { loadAdapterConfig } from './product-config.js';
4
+ import { log } from './fmt.js';
5
+ import * as fsMod from 'node:fs';
6
+
7
+ interface GitOptions {
8
+ encoding?: BufferEncoding;
9
+ stdio?: SpawnSyncOptions['stdio'];
10
+ cwd?: string;
11
+ env?: NodeJS.ProcessEnv;
12
+ maxBuffer?: number;
13
+ [key: string]: unknown;
14
+ }
15
+
16
+ interface GitResult {
17
+ status: number | null;
18
+ signal: string | null;
19
+ stdout: string;
20
+ stderr: string;
21
+ error?: Error | null;
22
+ }
23
+
24
+ export type GitFn = (args: string[], options?: GitOptions) => GitResult;
25
+
26
+ export interface VerificationAdapterConfig {
27
+ command: string | null;
28
+ defaultArea: string;
29
+ }
30
+
31
+ export interface VerificationProof {
32
+ rootDir: string;
33
+ area: string;
34
+ command: string | null;
35
+ commit: string;
36
+ tree: string;
37
+ verifiedAt: string;
38
+ }
39
+
40
+ export interface PublishedTreeStateOk {
41
+ ok: true;
42
+ rootDir: string;
43
+ commit: string;
44
+ tree: string;
45
+ }
46
+
47
+ export interface PublishedTreeStateFail {
48
+ ok: false;
49
+ error: string;
50
+ }
51
+
52
+ export type PublishedTreeState = PublishedTreeStateOk | PublishedTreeStateFail;
53
+
54
+ // parallix targets arbitrary repositories, so there is no universal gate
55
+ // command. When adapters.verification.command is not configured, verification
56
+ // is a no-op pass ("no validation"). A repository opts into a real gate by
57
+ // declaring the command in workflow.config.json.
58
+ export const DEFAULT_AREA = 'docs';
59
+ // Shell-safe no-op so this is harmless if pasted into a command sequence: `:` is
60
+ // the bash null command and `#` comments the explanation.
61
+ export const NO_GATE_NOTICE = ': # no verification gate configured (set adapters.verification.command)';
62
+
63
+ export function resolveVerificationAdapter(rootDir: string = process.cwd()): VerificationAdapterConfig {
64
+ const config = loadAdapterConfig(rootDir);
65
+ const verification = (config.verification as { command?: unknown; defaultArea?: unknown }) || {};
66
+ const command = typeof verification.command === 'string' && verification.command.trim()
67
+ ? verification.command.trim()
68
+ : null;
69
+ const defaultArea = typeof verification.defaultArea === 'string' && verification.defaultArea.trim()
70
+ ? verification.defaultArea.trim()
71
+ : DEFAULT_AREA;
72
+
73
+ return { command, defaultArea };
74
+ }
75
+
76
+ /** @param {string} [area] @param {string} [rootDir] */
77
+ export function formatVerificationCommand(area: string | undefined, rootDir: string = process.cwd()): string {
78
+ const { command, defaultArea } = resolveVerificationAdapter(rootDir);
79
+ const effectiveArea = area || defaultArea;
80
+ if (!command) {
81
+ return NO_GATE_NOTICE;
82
+ }
83
+ return command.replaceAll('{{area}}', effectiveArea || DEFAULT_AREA);
84
+ }
85
+
86
+ /** @param {string} [area] @param {{rootDir?: string, log?: Function, stdio?: string, runFn?: Function}} [options] */
87
+ export function runVerificationGate(area: string | undefined, options: { rootDir?: string; log?: Function; stdio?: string; runFn?: Function } = {}): import('child_process').SpawnSyncReturns<string> {
88
+ const opts = options;
89
+ const rootDir = opts.rootDir || process.cwd();
90
+ const { command, defaultArea } = resolveVerificationAdapter(rootDir);
91
+ const effectiveArea = area || defaultArea;
92
+
93
+ if (!command) {
94
+ const info = opts.log || log.info;
95
+ info(`No verification gate configured for area: ${effectiveArea}; default is no validation. `
96
+ + 'Set adapters.verification.command in workflow.config.json to enforce one.');
97
+ return { status: 0 } as import('child_process').SpawnSyncReturns<string>;
98
+ }
99
+
100
+ const stdio = opts.stdio || 'inherit';
101
+ const runFn = opts.runFn || run;
102
+ return runFn('bash', ['-lc', command.replaceAll('{{area}}', effectiveArea)], { cwd: rootDir, stdio });
103
+ }
104
+
105
+ /** @param {string} rootDir @param {{gitRunner?: GitFn}} [options] */
106
+ export function readPublishedTreeState(rootDir: string, options: { gitRunner?: GitFn } = {}): PublishedTreeState {
107
+ const gitRunner = options.gitRunner || git;
108
+ const resolvedRoot = fsMod.realpathSync(rootDir);
109
+ const commitResult = gitRunner(['-C', resolvedRoot, 'rev-parse', 'HEAD']);
110
+ const treeResult = gitRunner(['-C', resolvedRoot, 'rev-parse', 'HEAD^{tree}']);
111
+
112
+ const commit = commitResult.stdout ? commitResult.stdout.trim() : '';
113
+ const tree = treeResult.stdout ? treeResult.stdout.trim() : '';
114
+ if (commitResult.status !== 0 || treeResult.status !== 0 || !commit || !tree) {
115
+ return {
116
+ ok: false,
117
+ error: `could not resolve current published tree for ${resolvedRoot}`
118
+ };
119
+ }
120
+
121
+ return { ok: true, rootDir: resolvedRoot, commit, tree };
122
+ }
123
+
124
+ /** @param {string} [area] @param {string} [rootDir] @param {{gitRunner?: GitFn, runFn?: Function, stdio?: string}} [options] */
125
+ export function captureVerifiedTreeProof(area: string | undefined, rootDir: string = process.cwd(), options: { gitRunner?: GitFn; runFn?: Function; stdio?: string } = {}): { ok: boolean; proof?: VerificationProof; error?: string } {
126
+ const {
127
+ gitRunner = git,
128
+ runFn = run,
129
+ stdio = 'inherit'
130
+ } = options;
131
+
132
+ const before = readPublishedTreeState(rootDir, { gitRunner });
133
+ if (!before.ok) {return before;}
134
+
135
+ const verification = runVerificationGate(area, {
136
+ rootDir: before.rootDir,
137
+ runFn,
138
+ stdio
139
+ });
140
+ if (verification.status !== 0) {
141
+ return {
142
+ ok: false,
143
+ error: `verification gate failed for ${before.rootDir} with exit code ${verification.status}`
144
+ };
145
+ }
146
+
147
+ const after = readPublishedTreeState(before.rootDir!, { gitRunner });
148
+ if (!after.ok) {return after;}
149
+ if (after.commit !== before.commit || after.tree !== before.tree) {
150
+ return {
151
+ ok: false,
152
+ error: `verification proof became stale while publishing ${before.rootDir}`
153
+ };
154
+ }
155
+
156
+ const { command, defaultArea } = resolveVerificationAdapter(before.rootDir!);
157
+ const effectiveArea = area || defaultArea;
158
+
159
+ return {
160
+ ok: true,
161
+ proof: {
162
+ rootDir: before.rootDir!,
163
+ area: effectiveArea,
164
+ command: command || null,
165
+ commit: after.commit,
166
+ tree: after.tree,
167
+ verifiedAt: new Date().toISOString()
168
+ }
169
+ };
170
+ }
171
+
172
+ /** @param {{rootDir?: string, commit?: string, tree?: string}} proof @param {string} [rootDir] @param {{gitRunner?: GitFn}} [opts] */
173
+ export function assertVerifiedTreeProof(proof: { rootDir?: string; commit?: string; tree?: string }, rootDir: string = process.cwd(), opts: { gitRunner?: GitFn } = {}): { ok: boolean; proof?: PublishedTreeState; error?: string } {
174
+ if (!proof || typeof proof !== 'object') {
175
+ return { ok: false, error: 'missing verification proof' };
176
+ }
177
+
178
+ const o = opts;
179
+ const gitRunner = o.gitRunner || git;
180
+ const current = readPublishedTreeState(rootDir, { gitRunner });
181
+ if (!current.ok) {return current;}
182
+
183
+ if (proof.rootDir !== current.rootDir) {
184
+ return { ok: false, error: `verification proof was captured from a different checkout: ${proof.rootDir}` };
185
+ }
186
+ if (proof.commit !== current.commit || proof.tree !== current.tree) {
187
+ return { ok: false, error: 'verification proof does not match the tree being published' };
188
+ }
189
+
190
+ return { ok: true, proof: current };
191
+ }
192
+
193
+ /** @param {string[]} args @param {{log?: Function}} [options] */
194
+ export default function runWorkflow(args: string[], options: { log?: Function } = {}): import('child_process').SpawnSyncReturns<string> {
195
+ const opts = options;
196
+ const logFn = opts.log || log.plain;
197
+ const area = args[0] || process.env.VERIFY_AREA || DEFAULT_AREA;
198
+ logFn(`Running verification gate for area: ${area}...`);
199
+ return runVerificationGate(area, { stdio: 'inherit' });
200
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magnusekdahl/parallix",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "AI mission workflow toolkit with a px CLI — local-first, human-in-the-loop multi-agent development",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "private": false,
@@ -48,7 +48,9 @@
48
48
  ],
49
49
  "scripts": {
50
50
  "build": "tsc",
51
- "prepublishOnly": "test -z \"$(find . -name '*.ts' ! -path './node_modules/*')\" || tsc",
51
+ "build:cjs": "tsc --rootDir . --outDir . --module CommonJS --moduleResolution Node --esModuleInterop",
52
+ "prepublishOnly": "npm run build:cjs",
53
+ "pretest": "npm run build:cjs",
52
54
  "test": "FORCE_COLOR=0 node --test test/*.test.js",
53
55
  "test:coverage": "FORCE_COLOR=0 node lib/commands/coverage-gate.js --lcov",
54
56
  "typecheck": "tsc --noEmit"
package/prompts/draft.md CHANGED
@@ -24,6 +24,7 @@ Drafting requirements:
24
24
  - {{classificationInstructions}}
25
25
  - preserve `{{taskPath}}`: update content as needed but do not delete, rename, or move the file
26
26
  - do not edit the backlog `assignee` field; the workflow records ownership itself
27
+ - Refinement Signals section must use net engineering lines (NEL) bucket format (`Predicted NEL bucket: Small (0–80) / Medium (81–235) / Large (235+)`) and must NOT use the old agent-percentage-usage format
27
28
 
28
29
  Bug-labeled missions (regression-test-first / "lock the bug"):
29
30
  - this section applies only when the backlog task at `{{taskPath}}` carries a `bug` label (in addition to its `ai_sdlc` or `user_value` classification). If there is no `bug` label, ignore this section entirely.
@@ -14,7 +14,7 @@ Output: propose exactly 10 candidate missions. For each, provide:
14
14
  - Primary leverage (confidence / credibility / promotion signal)
15
15
  - Primary skill domains (BE / Web / Android / iOS / Ops / Security)
16
16
  - Estimated scope rank (1–10, 1 = largest)
17
- - Estimated agent % usage limit (`n/a` if not ready)
17
+ - Predicted NEL bucket (`n/a` if not ready)
18
18
  - Selection note (`activate as-is` | `split first` | `defer` | `n/a`)
19
19
  - Main risk mitigated (skill rot / credibility gap / system weakness)
20
20
  - Duplicate check: none, or follow-up of <existing mission>
package/px.js CHANGED
@@ -61,8 +61,6 @@ function shellInit(shell = 'bash') {
61
61
  ' if [ "$_px_current" != "$(cd "$_px_target" && pwd -P)" ]; then',
62
62
  ' cd "$_px_target" && echo "[px] Switched terminal context to: $(pwd)"',
63
63
  ' fi',
64
- ' else',
65
- ' echo "[px] ERROR: target directory \'$_px_target\' not found." >&2',
66
64
  ' fi',
67
65
  ' fi',
68
66
  ' return $_px_exit',