@magnusekdahl/parallix 1.2.0 → 1.3.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/index.js +190 -185
- package/lib/agents/agents.js +737 -838
- package/lib/agents/agents.ts +930 -0
- package/lib/agents/claude-telemetry.js +149 -154
- package/lib/agents/claude-telemetry.ts +233 -0
- package/lib/agents/claude.js +121 -131
- package/lib/agents/claude.ts +156 -0
- package/lib/agents/codex-telemetry.js +176 -164
- package/lib/agents/codex-telemetry.ts +205 -0
- package/lib/agents/codex.js +168 -201
- package/lib/agents/codex.ts +236 -0
- package/lib/agents/limit-hit.js +216 -226
- package/lib/agents/limit-hit.ts +262 -0
- package/lib/agents/mistral-telemetry.js +7 -10
- package/lib/agents/mistral-telemetry.ts +44 -0
- package/lib/agents/mistral.js +40 -58
- package/lib/agents/mistral.ts +84 -0
- package/lib/agents/opencode-export.js +126 -127
- package/lib/agents/opencode-export.ts +160 -0
- package/lib/agents/opencode-telemetry.js +282 -261
- package/lib/agents/opencode-telemetry.ts +350 -0
- package/lib/agents/opencode.js +263 -305
- package/lib/agents/opencode.ts +370 -0
- package/lib/agents/stage-telemetry.js +23 -24
- package/lib/agents/stage-telemetry.ts +47 -0
- package/lib/commands/active.js +502 -579
- package/lib/commands/active.ts +665 -0
- package/lib/commands/checkpoint.js +98 -74
- package/lib/commands/checkpoint.ts +79 -0
- package/lib/commands/config.js +61 -31
- package/lib/commands/config.ts +47 -0
- package/lib/commands/coverage-gate.js +271 -246
- package/lib/commands/coverage-gate.ts +362 -0
- package/lib/commands/diff.js +140 -119
- package/lib/commands/diff.ts +125 -0
- package/lib/commands/draft.js +770 -923
- package/lib/commands/draft.ts +1021 -0
- package/lib/commands/handoff.js +588 -608
- package/lib/commands/handoff.ts +694 -0
- package/lib/commands/integrate.js +1332 -1463
- package/lib/commands/integrate.ts +1688 -0
- package/lib/commands/mission-start.js +279 -237
- package/lib/commands/mission-start.ts +263 -0
- package/lib/commands/rebase.js +606 -582
- package/lib/commands/rebase.ts +635 -0
- package/lib/commands/repair-handoff.js +181 -179
- package/lib/commands/repair-handoff.ts +228 -0
- package/lib/commands/resolve-conflict.js +121 -102
- package/lib/commands/resolve-conflict.ts +112 -0
- package/lib/commands/review.js +3 -4
- package/lib/commands/review.ts +14 -0
- package/lib/commands/setup-review.js +4 -5
- package/lib/commands/setup-review.ts +14 -0
- package/lib/commands/setup.js +3 -3
- package/lib/commands/setup.ts +2 -0
- package/lib/commands/stats-backfill.js +394 -359
- package/lib/commands/stats-backfill.ts +418 -0
- package/lib/commands/stats.js +1424 -1665
- package/lib/commands/stats.ts +2186 -0
- package/lib/commands/status.js +219 -200
- package/lib/commands/status.ts +219 -0
- package/lib/commands/verify.js +3 -1
- package/lib/commands/verify.ts +2 -0
- package/lib/core/fmt.js +140 -211
- package/lib/core/fmt.ts +189 -0
- package/lib/core/git.js +121 -102
- package/lib/core/git.ts +146 -0
- package/lib/core/gitignore.js +59 -106
- package/lib/core/gitignore.ts +103 -0
- package/lib/core/mission-utils.js +833 -875
- package/lib/core/mission-utils.ts +998 -0
- package/lib/core/nels.js +112 -127
- package/lib/core/nels.ts +189 -0
- package/lib/core/persistent-data-migration.js +247 -205
- package/lib/core/persistent-data-migration.ts +255 -0
- package/lib/core/product-config.js +400 -476
- package/lib/core/product-config.ts +518 -0
- package/lib/core/runtime-matrix.js +41 -87
- package/lib/core/runtime-matrix.ts +84 -0
- package/lib/core/spawn-tee.js +152 -192
- package/lib/core/spawn-tee.ts +202 -0
- package/lib/core/state-map.js +92 -92
- package/lib/core/state-map.ts +115 -0
- package/lib/core/storage.js +101 -129
- package/lib/core/storage.ts +182 -0
- package/lib/core/subagent-limit.js +15 -16
- package/lib/core/subagent-limit.ts +25 -0
- package/lib/core/verification.js +148 -138
- package/lib/core/verification.ts +200 -0
- package/lib/index.js +142 -59
- package/lib/index.ts +134 -0
- package/lib/review/rebase.js +142 -145
- package/lib/review/rebase.ts +188 -0
- package/lib/review/review-adapter.js +99 -87
- package/lib/review/review-adapter.ts +198 -0
- package/lib/review/review-artifacts.js +486 -629
- package/lib/review/review-artifacts.ts +622 -0
- package/lib/review/review-commands.js +1220 -1373
- package/lib/review/review-commands.ts +1579 -0
- package/lib/review/review-events.js +544 -886
- package/lib/review/review-events.ts +954 -0
- package/lib/review/review-loop.js +945 -1020
- package/lib/review/review-loop.ts +1174 -0
- package/lib/review/review-polling.js +86 -115
- package/lib/review/review-polling.ts +194 -0
- package/lib/review/review-prompts.js +154 -189
- package/lib/review/review-prompts.ts +182 -0
- package/lib/review/review-state.js +246 -225
- package/lib/review/review-state.ts +353 -0
- package/lib/review/review.js +63 -84
- package/lib/review/review.ts +138 -0
- package/lib/tools/backlog.js +694 -680
- package/lib/tools/backlog.ts +835 -0
- package/lib/tools/forgejo.js +1270 -1405
- package/lib/tools/forgejo.ts +1793 -0
- package/lib/tools/gatekeeper.js +106 -96
- package/lib/tools/gatekeeper.ts +124 -0
- package/lib/tools/redgreen.js +200 -181
- package/lib/tools/redgreen.ts +221 -0
- package/lib/tools/sessions.js +58 -52
- package/lib/tools/sessions.ts +81 -0
- package/lib/tools/setup-review.js +902 -975
- package/lib/tools/setup-review.ts +1152 -0
- package/package.json +7 -5
- package/prompts/draft.md +1 -0
- package/prompts/portfolio.md +1 -1
- package/px.js +226 -210
|
@@ -1,536 +1,460 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_CONFIG = exports.REQUIRED_ADAPTER_KEYS = void 0;
|
|
7
|
+
exports.isPlainObject = isPlainObject;
|
|
8
|
+
exports.deepMerge = deepMerge;
|
|
9
|
+
exports.defaultConfig = defaultConfig;
|
|
10
|
+
exports.configCandidates = configCandidates;
|
|
11
|
+
exports.findWorkflowConfig = findWorkflowConfig;
|
|
12
|
+
exports.loadWorkflowConfig = loadWorkflowConfig;
|
|
13
|
+
exports.loadEffectiveConfig = loadEffectiveConfig;
|
|
14
|
+
exports.validateWorkflowConfig = validateWorkflowConfig;
|
|
15
|
+
exports.detectLegacyRepoLayout = detectLegacyRepoLayout;
|
|
16
|
+
exports.isStandaloneWorkflowLayout = isStandaloneWorkflowLayout;
|
|
17
|
+
exports.hasGitRepository = hasGitRepository;
|
|
18
|
+
exports.initializeGitRepository = initializeGitRepository;
|
|
19
|
+
exports.gitIdentityEnv = gitIdentityEnv;
|
|
20
|
+
exports.commitWorkflowBaseline = commitWorkflowBaseline;
|
|
21
|
+
exports.ensureStandaloneMissionBaseline = ensureStandaloneMissionBaseline;
|
|
22
|
+
exports.ensureStandaloneGitRepo = ensureStandaloneGitRepo;
|
|
23
|
+
exports.adapterChecklist = adapterChecklist;
|
|
24
|
+
exports.loadAdapterConfig = loadAdapterConfig;
|
|
25
|
+
exports.resolveTaskStorage = resolveTaskStorage;
|
|
26
|
+
exports.resolveReviewAdapter = resolveReviewAdapter;
|
|
27
|
+
exports.isForgejoReviewEnabled = isForgejoReviewEnabled;
|
|
28
|
+
exports.resolveAgentAdapter = resolveAgentAdapter;
|
|
29
|
+
exports.resolveAgentModel = resolveAgentModel;
|
|
30
|
+
exports.evaluateRepositoryReadiness = evaluateRepositoryReadiness;
|
|
31
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
32
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
33
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
34
|
const REQUIRED_ADAPTER_KEYS = ['tasks', 'missions', 'verification', 'review', 'agents'];
|
|
6
|
-
|
|
7
|
-
// Code-owned defaults. An absent workflow.config.json yields a working tool;
|
|
8
|
-
// the optional override file overrides only the keys it sets. These defaults
|
|
9
|
-
// are the single source of truth — there is no shipped example config, so there
|
|
10
|
-
// is no second source to drift from (see task-1233 Scope Amendment).
|
|
35
|
+
exports.REQUIRED_ADAPTER_KEYS = REQUIRED_ADAPTER_KEYS;
|
|
11
36
|
const DEFAULT_CONFIG = Object.freeze({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
product: {
|
|
38
|
+
name: 'Workflow',
|
|
39
|
+
targetUser: 'Engineering teams using git, task tracking, and code review',
|
|
40
|
+
},
|
|
41
|
+
adapters: {
|
|
42
|
+
tasks: { provider: 'backlog-md', storage: 'backlog', stateMap: 'state-map.json' },
|
|
43
|
+
missions: {
|
|
44
|
+
baseDir: 'missions',
|
|
45
|
+
branchPrefix: 'mission/',
|
|
46
|
+
worktreePattern: '../<repo>-<slug>',
|
|
47
|
+
},
|
|
48
|
+
verification: { defaultArea: 'docs' },
|
|
49
|
+
stats: { path: 'stats.csv' },
|
|
50
|
+
review: {},
|
|
51
|
+
agents: {},
|
|
22
52
|
},
|
|
23
|
-
// No universal cross-repo gate exists, so the default is no validation
|
|
24
|
-
// (no command). A repository opts into a gate by declaring
|
|
25
|
-
// adapters.verification.command in workflow.config.json.
|
|
26
|
-
verification: { defaultArea: 'docs' },
|
|
27
|
-
stats: { path: 'stats.csv' },
|
|
28
|
-
// Unset provider currently keeps Forgejo enabled (isForgejoReviewEnabled
|
|
29
|
-
// treats null as enabled) — a WrGroceries-ism. Flipping review off-by-default
|
|
30
|
-
// for external repos has a large review-subsystem blast radius and is deferred
|
|
31
|
-
// to TASK-1244; WrGroceries already declares review.provider in its own config.
|
|
32
|
-
review: {},
|
|
33
|
-
agents: {},
|
|
34
|
-
},
|
|
35
53
|
});
|
|
36
|
-
|
|
37
|
-
/** @param {unknown} value */
|
|
54
|
+
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
38
55
|
function isPlainObject(value) {
|
|
39
|
-
|
|
56
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
40
57
|
}
|
|
41
|
-
|
|
42
|
-
/** @param {Record<string, unknown>} base @param {Record<string, unknown>} override */
|
|
43
58
|
function deepMerge(base, override) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
if (!isPlainObject(override)) {
|
|
60
|
+
return base;
|
|
61
|
+
}
|
|
62
|
+
const out = { ...base };
|
|
63
|
+
for (const [key, value] of Object.entries(override)) {
|
|
64
|
+
const val = out[key];
|
|
65
|
+
out[key] = isPlainObject(value) && isPlainObject(val)
|
|
66
|
+
? deepMerge(val, value)
|
|
67
|
+
: value;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
53
70
|
}
|
|
54
|
-
|
|
55
|
-
// A fresh deep clone of the defaults so callers can never mutate the frozen
|
|
56
|
-
// source.
|
|
57
71
|
function defaultConfig() {
|
|
58
|
-
|
|
72
|
+
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
59
73
|
}
|
|
60
|
-
|
|
61
74
|
function configCandidates(rootDir = process.cwd()) {
|
|
62
|
-
|
|
75
|
+
return [node_path_1.default.join(rootDir, 'workflow.config.json')];
|
|
63
76
|
}
|
|
64
|
-
|
|
65
77
|
function findWorkflowConfig(rootDir = process.cwd()) {
|
|
66
|
-
|
|
78
|
+
return configCandidates(rootDir).find(candidate => node_fs_1.default.existsSync(candidate)) || null;
|
|
67
79
|
}
|
|
68
|
-
|
|
69
80
|
function loadWorkflowConfig(rootDir = process.cwd()) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
81
|
+
const configPath = findWorkflowConfig(rootDir);
|
|
82
|
+
if (!configPath) {
|
|
83
|
+
return { found: false, configPath: null, config: null, parseError: null };
|
|
84
|
+
}
|
|
85
|
+
let raw;
|
|
86
|
+
try {
|
|
87
|
+
raw = node_fs_1.default.readFileSync(configPath, 'utf8');
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const e = error;
|
|
91
|
+
if (e && e.code === 'ENOENT') {
|
|
92
|
+
return { found: false, configPath: null, config: null, parseError: null };
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return {
|
|
98
|
+
found: true,
|
|
99
|
+
configPath,
|
|
100
|
+
config: JSON.parse(raw),
|
|
101
|
+
parseError: null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
found: true,
|
|
107
|
+
configPath,
|
|
108
|
+
config: null,
|
|
109
|
+
parseError: error,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
101
112
|
}
|
|
102
|
-
|
|
103
|
-
// The effective config a command sees: code-owned defaults with any override
|
|
104
|
-
// file merged on top. A missing or malformed override falls back to defaults.
|
|
105
113
|
function loadEffectiveConfig(rootDir = process.cwd()) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return defaultConfig();
|
|
114
|
-
}
|
|
115
|
-
return deepMerge(defaultConfig(), loaded.config);
|
|
114
|
+
const loaded = loadWorkflowConfig(rootDir);
|
|
115
|
+
if (!loaded.found || loaded.parseError || !isPlainObject(loaded.config)) {
|
|
116
|
+
return defaultConfig();
|
|
117
|
+
}
|
|
118
|
+
if (validateWorkflowConfig(loaded.config).length > 0) {
|
|
119
|
+
return defaultConfig();
|
|
120
|
+
}
|
|
121
|
+
return deepMerge(defaultConfig(), loaded.config);
|
|
116
122
|
}
|
|
117
|
-
|
|
118
|
-
// The override file is optional and partial: validate only the shape of what it
|
|
119
|
-
// provides. Missing sections are filled by code-owned defaults, so absent
|
|
120
|
-
// sections are not errors (and there are no placeholder sentinels to detect).
|
|
121
|
-
/** @param {unknown} config */
|
|
122
123
|
function validateWorkflowConfig(config) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
124
|
+
if (!isPlainObject(config)) {
|
|
125
|
+
return ['top-level JSON object is required'];
|
|
126
|
+
}
|
|
127
|
+
const cfg = config;
|
|
128
|
+
const issues = [];
|
|
129
|
+
if ('product' in cfg && !isPlainObject(cfg.product)) {
|
|
130
|
+
issues.push('product must be an object');
|
|
131
|
+
}
|
|
132
|
+
if ('adapters' in cfg) {
|
|
133
|
+
const adapters = cfg.adapters;
|
|
134
|
+
if (!isPlainObject(adapters)) {
|
|
135
|
+
issues.push('adapters must be an object');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
for (const [key, value] of Object.entries(adapters)) {
|
|
139
|
+
if (!isPlainObject(value)) {
|
|
140
|
+
issues.push(`adapters.${key} must be an object`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
141
143
|
}
|
|
142
|
-
}
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
-
return issues;
|
|
145
|
+
return issues;
|
|
146
146
|
}
|
|
147
|
-
|
|
148
147
|
function detectLegacyRepoLayout(rootDir = process.cwd()) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return fs.existsSync(backlogDir) && fs.existsSync(missionDocsDir) && fs.existsSync(verifyScript);
|
|
148
|
+
const backlogDir = node_path_1.default.join(rootDir, 'backlog');
|
|
149
|
+
const missionDocsDir = node_path_1.default.join(rootDir, 'docs', 'missions');
|
|
150
|
+
const verifyScript = node_path_1.default.join(rootDir, 'scripts', 'verify-local.sh');
|
|
151
|
+
return node_fs_1.default.existsSync(backlogDir) && node_fs_1.default.existsSync(missionDocsDir) && node_fs_1.default.existsSync(verifyScript);
|
|
154
152
|
}
|
|
155
|
-
|
|
156
153
|
function isStandaloneWorkflowLayout(rootDir = process.cwd()) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return fs.existsSync(workflowIndex) && fs.existsSync(workflowConfig);
|
|
154
|
+
const workflowIndex = node_path_1.default.join(rootDir, 'workflow', 'index.js');
|
|
155
|
+
const workflowConfig = node_path_1.default.join(rootDir, 'workflow.config.json');
|
|
156
|
+
return node_fs_1.default.existsSync(workflowIndex) && node_fs_1.default.existsSync(workflowConfig);
|
|
161
157
|
}
|
|
162
|
-
|
|
163
158
|
function hasGitRepository(rootDir = process.cwd()) {
|
|
164
|
-
|
|
159
|
+
return node_fs_1.default.existsSync(node_path_1.default.join(rootDir, '.git'));
|
|
165
160
|
}
|
|
166
|
-
|
|
167
|
-
/** @param {string} [rootDir] @param {{spawnSyncFn?: typeof spawnSync}} [options] */
|
|
168
161
|
function initializeGitRepository(rootDir = process.cwd(), options = {}) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
encoding: 'utf8',
|
|
193
|
-
});
|
|
194
|
-
return { ok: true, branch: 'main', mode: 'init-fallback' };
|
|
162
|
+
const spawnSyncFn = options.spawnSyncFn || node_child_process_1.spawnSync;
|
|
163
|
+
const initMain = spawnSyncFn('git', ['init', '-b', 'main'], {
|
|
164
|
+
cwd: rootDir,
|
|
165
|
+
encoding: 'utf8',
|
|
166
|
+
});
|
|
167
|
+
if (initMain.status === 0) {
|
|
168
|
+
return { ok: true, branch: 'main', mode: 'init-main' };
|
|
169
|
+
}
|
|
170
|
+
const initFallback = spawnSyncFn('git', ['init'], {
|
|
171
|
+
cwd: rootDir,
|
|
172
|
+
encoding: 'utf8',
|
|
173
|
+
});
|
|
174
|
+
if (initFallback.status !== 0) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
message: (initFallback.stderr || initFallback.stdout || initMain.stderr || initMain.stdout || 'git init failed').trim(),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
spawnSyncFn('git', ['symbolic-ref', 'HEAD', 'refs/heads/main'], {
|
|
181
|
+
cwd: rootDir,
|
|
182
|
+
encoding: 'utf8',
|
|
183
|
+
});
|
|
184
|
+
return { ok: true, branch: 'main', mode: 'init-fallback' };
|
|
195
185
|
}
|
|
196
|
-
|
|
197
|
-
// Commits the workflow files and any config that exists so the freshly
|
|
198
|
-
// initialized repo has a baseline commit. Without this, mission worktrees
|
|
199
|
-
// created via `git worktree add` would not have workflow/ or
|
|
200
|
-
// workflow.config.json checked out and could not run any workflow command.
|
|
201
186
|
function gitIdentityEnv() {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
187
|
+
const env = { ...process.env };
|
|
188
|
+
if (!env.GIT_AUTHOR_NAME) {
|
|
189
|
+
env.GIT_AUTHOR_NAME = 'Workflow Setup';
|
|
190
|
+
}
|
|
191
|
+
if (!env.GIT_AUTHOR_EMAIL) {
|
|
192
|
+
env.GIT_AUTHOR_EMAIL = 'workflow@example.invalid';
|
|
193
|
+
}
|
|
194
|
+
if (!env.GIT_COMMITTER_NAME) {
|
|
195
|
+
env.GIT_COMMITTER_NAME = env.GIT_AUTHOR_NAME;
|
|
196
|
+
}
|
|
197
|
+
if (!env.GIT_COMMITTER_EMAIL) {
|
|
198
|
+
env.GIT_COMMITTER_EMAIL = env.GIT_AUTHOR_EMAIL;
|
|
199
|
+
}
|
|
200
|
+
return env;
|
|
208
201
|
}
|
|
209
|
-
|
|
210
|
-
/** @param {string} rootDir @param {{spawnSyncFn?: typeof spawnSync, existsSyncFn?: typeof fs.existsSync}} [options] */
|
|
211
202
|
function commitWorkflowBaseline(rootDir, options = {}) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (commitResult.status !== 0) {
|
|
240
|
-
return {
|
|
241
|
-
ok: false,
|
|
242
|
-
committed: false,
|
|
243
|
-
message: (commitResult.stderr || commitResult.stdout || 'git commit failed').trim(),
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return { ok: true, committed: true, files: present };
|
|
203
|
+
const spawnSyncFn = options.spawnSyncFn || node_child_process_1.spawnSync;
|
|
204
|
+
const existsSyncFn = options.existsSyncFn || node_fs_1.default.existsSync;
|
|
205
|
+
const candidates = ['workflow', 'workflow.config.json'];
|
|
206
|
+
const present = candidates.filter(name => existsSyncFn(node_path_1.default.join(rootDir, name)));
|
|
207
|
+
if (present.length === 0) {
|
|
208
|
+
return { ok: true, committed: false, reason: 'no-workflow-files' };
|
|
209
|
+
}
|
|
210
|
+
const addResult = spawnSyncFn('git', ['add', '-A', '.'], {
|
|
211
|
+
cwd: rootDir,
|
|
212
|
+
encoding: 'utf8',
|
|
213
|
+
});
|
|
214
|
+
if (addResult.status !== 0) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
committed: false,
|
|
218
|
+
message: (addResult.stderr || addResult.stdout || 'git add failed').trim(),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const commitResult = spawnSyncFn('git', ['commit', '-m', 'workflow: initial setup'], { cwd: rootDir, encoding: 'utf8', env: gitIdentityEnv() });
|
|
222
|
+
if (commitResult.status !== 0) {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
committed: false,
|
|
226
|
+
message: (commitResult.stderr || commitResult.stdout || 'git commit failed').trim(),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return { ok: true, committed: true, files: present };
|
|
248
230
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
231
|
+
function ensureStandaloneMissionBaseline(rootDir = process.cwd(), { spawnSyncFn = node_child_process_1.spawnSync } = {}) {
|
|
232
|
+
if (!isStandaloneWorkflowLayout(rootDir) || !hasGitRepository(rootDir)) {
|
|
233
|
+
return { changed: false, committed: false, skipped: true };
|
|
234
|
+
}
|
|
235
|
+
const statusResult = spawnSyncFn('git', ['status', '--porcelain'], {
|
|
236
|
+
cwd: rootDir,
|
|
237
|
+
encoding: 'utf8',
|
|
238
|
+
});
|
|
239
|
+
if (statusResult.status !== 0) {
|
|
240
|
+
return {
|
|
241
|
+
changed: false,
|
|
242
|
+
committed: false,
|
|
243
|
+
failed: true,
|
|
244
|
+
message: (statusResult.stderr || statusResult.stdout || 'git status failed').trim(),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const dirtyEntries = (statusResult.stdout || '')
|
|
248
|
+
.split('\n')
|
|
249
|
+
.map(line => line.trimEnd())
|
|
250
|
+
.filter(Boolean);
|
|
251
|
+
const relevantEntries = dirtyEntries.filter(line => {
|
|
252
|
+
const filePath = line.slice(3).trim();
|
|
253
|
+
return !filePath.startsWith('.workflow/') && !filePath.startsWith('.sessions/');
|
|
254
|
+
});
|
|
255
|
+
if (relevantEntries.length === 0) {
|
|
256
|
+
return { changed: dirtyEntries.length > 0, committed: false, skipped: false };
|
|
257
|
+
}
|
|
258
|
+
const conflicted = relevantEntries.filter(line => {
|
|
259
|
+
const state = line.slice(0, 2).trim();
|
|
260
|
+
return ['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'].includes(state);
|
|
261
|
+
});
|
|
262
|
+
if (conflicted.length > 0) {
|
|
263
|
+
return {
|
|
264
|
+
changed: true,
|
|
265
|
+
committed: false,
|
|
266
|
+
failed: true,
|
|
267
|
+
message: `conflicted files block mission baseline commit: ${conflicted.map(line => line.slice(3).trim()).join(', ')}`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const addResult = spawnSyncFn('git', ['add', '-A', '.'], {
|
|
271
|
+
cwd: rootDir,
|
|
272
|
+
encoding: 'utf8',
|
|
273
|
+
});
|
|
274
|
+
if (addResult.status !== 0) {
|
|
275
|
+
return {
|
|
276
|
+
changed: true,
|
|
277
|
+
committed: false,
|
|
278
|
+
failed: true,
|
|
279
|
+
message: (addResult.stderr || addResult.stdout || 'git add failed').trim(),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const commitResult = spawnSyncFn('git', ['commit', '-m', 'workflow: prepare standalone mission baseline'], {
|
|
283
|
+
cwd: rootDir,
|
|
284
|
+
encoding: 'utf8',
|
|
285
|
+
env: gitIdentityEnv(),
|
|
286
|
+
});
|
|
287
|
+
if (commitResult.status !== 0) {
|
|
288
|
+
return {
|
|
289
|
+
changed: true,
|
|
290
|
+
committed: false,
|
|
291
|
+
failed: true,
|
|
292
|
+
message: (commitResult.stderr || commitResult.stdout || 'git commit failed').trim(),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
313
295
|
return {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
message: (commitResult.stderr || commitResult.stdout || 'git commit failed').trim(),
|
|
296
|
+
changed: true,
|
|
297
|
+
committed: true,
|
|
298
|
+
entries: relevantEntries.map(line => line.slice(3).trim()),
|
|
318
299
|
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
changed: true,
|
|
323
|
-
committed: true,
|
|
324
|
-
entries: relevantEntries.map(line => line.slice(3).trim()),
|
|
325
|
-
};
|
|
326
300
|
}
|
|
327
|
-
|
|
328
|
-
/** @param {string} [rootDir] @param {{isStandaloneWorkflowLayoutFn?: Function, hasGitRepositoryFn?: Function, initializeGitRepositoryFn?: Function, commitWorkflowBaselineFn?: Function}} [options] */
|
|
329
301
|
function ensureStandaloneGitRepo(rootDir = process.cwd(), options = {}) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
302
|
+
const opts = options;
|
|
303
|
+
const isStandaloneWorkflowLayoutFn = opts.isStandaloneWorkflowLayoutFn || isStandaloneWorkflowLayout;
|
|
304
|
+
const hasGitRepositoryFn = opts.hasGitRepositoryFn || hasGitRepository;
|
|
305
|
+
const initializeGitRepositoryFn = opts.initializeGitRepositoryFn || initializeGitRepository;
|
|
306
|
+
const commitWorkflowBaselineFn = opts.commitWorkflowBaselineFn || commitWorkflowBaseline;
|
|
307
|
+
if (!isStandaloneWorkflowLayoutFn(rootDir) || hasGitRepositoryFn(rootDir)) {
|
|
308
|
+
return { changed: false, initialized: false };
|
|
309
|
+
}
|
|
310
|
+
const initOpts = { spawnSyncFn: options.spawnSyncFn };
|
|
311
|
+
const result = initializeGitRepositoryFn(rootDir, initOpts);
|
|
312
|
+
if (!result.ok) {
|
|
313
|
+
return {
|
|
314
|
+
changed: false,
|
|
315
|
+
initialized: false,
|
|
316
|
+
failed: true,
|
|
317
|
+
message: result.message || 'git init failed',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const commitOpts = { spawnSyncFn: options.spawnSyncFn, existsSyncFn: options.existsSyncFn };
|
|
321
|
+
const commit = commitWorkflowBaselineFn(rootDir, commitOpts);
|
|
343
322
|
return {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
323
|
+
changed: true,
|
|
324
|
+
initialized: true,
|
|
325
|
+
branch: result.branch || 'main',
|
|
326
|
+
mode: result.mode || 'init-main',
|
|
327
|
+
baselineCommit: commit,
|
|
348
328
|
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const commit = commitWorkflowBaselineFn(rootDir, options);
|
|
352
|
-
|
|
353
|
-
return {
|
|
354
|
-
changed: true,
|
|
355
|
-
initialized: true,
|
|
356
|
-
branch: result.branch || 'main',
|
|
357
|
-
mode: result.mode || 'init-main',
|
|
358
|
-
baselineCommit: commit,
|
|
359
|
-
};
|
|
360
329
|
}
|
|
361
|
-
|
|
362
330
|
function adapterChecklist() {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
331
|
+
return [
|
|
332
|
+
'Workflow runs on built-in defaults; no config file is required to start',
|
|
333
|
+
'Create workflow.config.json only to override a default (schema: workflow/config/workflow.config.schema.json)',
|
|
334
|
+
'Run `px config` to print the effective configuration',
|
|
335
|
+
'Override adapters.tasks for a different task tracker or storage path',
|
|
336
|
+
'Override adapters.missions for mission document layout and branch/worktree conventions',
|
|
337
|
+
'Override adapters.verification for your repo gate command',
|
|
338
|
+
'Override adapters.review to enable a review provider and remote naming',
|
|
339
|
+
];
|
|
372
340
|
}
|
|
373
|
-
|
|
374
|
-
// Returns the override file's adapters object, or {} when there is no override.
|
|
375
|
-
// Each resolver (resolveTaskStorage, resolveVerificationAdapter, …) applies its
|
|
376
|
-
// own code-owned fallback on top of this, so an absent config still yields a
|
|
377
|
-
// fully working tool. loadEffectiveConfig (used by `node parallix config`) is
|
|
378
|
-
// the document-level view; the per-resolver fallbacks remain the runtime source
|
|
379
|
-
// of truth so this change does not shadow them.
|
|
380
341
|
function loadAdapterConfig(rootDir = process.cwd()) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
342
|
+
const explicit = loadWorkflowConfig(rootDir);
|
|
343
|
+
if (!explicit.found || explicit.parseError || !isPlainObject(explicit.config)) {
|
|
344
|
+
return {};
|
|
345
|
+
}
|
|
346
|
+
return isPlainObject(explicit.config.adapters) ? explicit.config.adapters : {};
|
|
386
347
|
}
|
|
387
|
-
|
|
388
348
|
function resolveTaskStorage(rootDir = process.cwd()) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const tasksAdapter = loadAdapterConfig(rootDir).tasks || {};
|
|
399
|
-
const storage = tasksAdapter.storagePath || tasksAdapter.storage;
|
|
400
|
-
if (!storage) {
|
|
401
|
-
return fallback;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (typeof storage === 'string') {
|
|
405
|
-
const storageDir = path.resolve(rootDir, storage);
|
|
406
|
-
const storageName = path.basename(storageDir);
|
|
407
|
-
if (storageName === 'tasks') {
|
|
408
|
-
const baseDir = path.dirname(storageDir);
|
|
409
|
-
return {
|
|
410
|
-
baseDir,
|
|
411
|
-
tasksDir: storageDir,
|
|
412
|
-
completedDir: path.join(baseDir, 'completed'),
|
|
413
|
-
archiveTasksDir: path.join(baseDir, 'archive', 'tasks'),
|
|
414
|
-
draftsDir: path.join(baseDir, 'drafts'),
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return {
|
|
419
|
-
baseDir: storageDir,
|
|
420
|
-
tasksDir: path.join(storageDir, 'tasks'),
|
|
421
|
-
completedDir: path.join(storageDir, 'completed'),
|
|
422
|
-
archiveTasksDir: path.join(storageDir, 'archive', 'tasks'),
|
|
423
|
-
draftsDir: path.join(storageDir, 'drafts'),
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (typeof storage === 'object' && !Array.isArray(storage)) {
|
|
428
|
-
const tasksDir = storage.tasksDir
|
|
429
|
-
? path.resolve(rootDir, storage.tasksDir)
|
|
430
|
-
: fallback.tasksDir;
|
|
431
|
-
const completedDir = storage.completedDir
|
|
432
|
-
? path.resolve(rootDir, storage.completedDir)
|
|
433
|
-
: path.join(path.dirname(tasksDir), 'completed');
|
|
434
|
-
const baseDir = path.dirname(tasksDir);
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
baseDir,
|
|
438
|
-
tasksDir,
|
|
439
|
-
completedDir,
|
|
440
|
-
archiveTasksDir: storage.archiveTasksDir
|
|
441
|
-
? path.resolve(rootDir, storage.archiveTasksDir)
|
|
442
|
-
: path.join(baseDir, 'archive', 'tasks'),
|
|
443
|
-
draftsDir: path.join(baseDir, 'drafts'),
|
|
349
|
+
const fallbackBaseDir = node_path_1.default.join(rootDir, 'backlog');
|
|
350
|
+
const fallback = {
|
|
351
|
+
baseDir: fallbackBaseDir,
|
|
352
|
+
tasksDir: node_path_1.default.join(fallbackBaseDir, 'tasks'),
|
|
353
|
+
completedDir: node_path_1.default.join(fallbackBaseDir, 'completed'),
|
|
354
|
+
archiveTasksDir: node_path_1.default.join(fallbackBaseDir, 'archive', 'tasks'),
|
|
355
|
+
draftsDir: node_path_1.default.join(fallbackBaseDir, 'drafts'),
|
|
444
356
|
};
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
357
|
+
const tasksAdapter = loadAdapterConfig(rootDir).tasks || {};
|
|
358
|
+
const storage = tasksAdapter.storagePath || tasksAdapter.storage;
|
|
359
|
+
if (!storage) {
|
|
360
|
+
return fallback;
|
|
361
|
+
}
|
|
362
|
+
if (typeof storage === 'string') {
|
|
363
|
+
const storageDir = node_path_1.default.resolve(rootDir, storage);
|
|
364
|
+
const storageName = node_path_1.default.basename(storageDir);
|
|
365
|
+
if (storageName === 'tasks') {
|
|
366
|
+
const baseDir = node_path_1.default.dirname(storageDir);
|
|
367
|
+
return {
|
|
368
|
+
baseDir,
|
|
369
|
+
tasksDir: storageDir,
|
|
370
|
+
completedDir: node_path_1.default.join(baseDir, 'completed'),
|
|
371
|
+
archiveTasksDir: node_path_1.default.join(baseDir, 'archive', 'tasks'),
|
|
372
|
+
draftsDir: node_path_1.default.join(baseDir, 'drafts'),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
baseDir: storageDir,
|
|
377
|
+
tasksDir: node_path_1.default.join(storageDir, 'tasks'),
|
|
378
|
+
completedDir: node_path_1.default.join(storageDir, 'completed'),
|
|
379
|
+
archiveTasksDir: node_path_1.default.join(storageDir, 'archive', 'tasks'),
|
|
380
|
+
draftsDir: node_path_1.default.join(storageDir, 'drafts'),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (typeof storage === 'object' && !Array.isArray(storage)) {
|
|
384
|
+
const storageObj = storage;
|
|
385
|
+
const tasksDir = storageObj.tasksDir
|
|
386
|
+
? node_path_1.default.resolve(rootDir, storageObj.tasksDir)
|
|
387
|
+
: fallback.tasksDir;
|
|
388
|
+
const completedDir = storageObj.completedDir
|
|
389
|
+
? node_path_1.default.resolve(rootDir, storageObj.completedDir)
|
|
390
|
+
: node_path_1.default.join(node_path_1.default.dirname(tasksDir), 'completed');
|
|
391
|
+
const baseDir = node_path_1.default.dirname(tasksDir);
|
|
392
|
+
return {
|
|
393
|
+
baseDir,
|
|
394
|
+
tasksDir,
|
|
395
|
+
completedDir,
|
|
396
|
+
archiveTasksDir: storageObj.archiveTasksDir
|
|
397
|
+
? node_path_1.default.resolve(rootDir, storageObj.archiveTasksDir)
|
|
398
|
+
: node_path_1.default.join(baseDir, 'archive', 'tasks'),
|
|
399
|
+
draftsDir: node_path_1.default.join(baseDir, 'drafts'),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return fallback;
|
|
448
403
|
}
|
|
449
|
-
|
|
450
404
|
function resolveReviewAdapter(rootDir = process.cwd()) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
405
|
+
const review = loadAdapterConfig(rootDir).review || {};
|
|
406
|
+
return {
|
|
407
|
+
provider: review.provider || null,
|
|
408
|
+
remote: review.remote || null,
|
|
409
|
+
baseUrl: review.baseUrl || null,
|
|
410
|
+
repo: review.repo || null,
|
|
411
|
+
};
|
|
458
412
|
}
|
|
459
|
-
|
|
460
413
|
function isForgejoReviewEnabled(rootDir = process.cwd()) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
414
|
+
const review = resolveReviewAdapter(rootDir);
|
|
415
|
+
if (review.provider === null) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
return review.provider === 'forgejo';
|
|
464
419
|
}
|
|
465
|
-
|
|
466
420
|
function resolveAgentAdapter() {
|
|
467
|
-
|
|
421
|
+
return {};
|
|
468
422
|
}
|
|
469
|
-
|
|
470
|
-
// Returns the configured LLM model string for an agent family, or null when the
|
|
471
|
-
// family is not listed under adapters.agents.models. A null return means the
|
|
472
|
-
// launcher omits the model parameter entirely so the agent uses its own default.
|
|
473
|
-
/** @param {string} agentFamily @param {string} [rootDir] */
|
|
474
423
|
function resolveAgentModel(agentFamily, rootDir = process.cwd()) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
424
|
+
if (!agentFamily || typeof agentFamily !== 'string') {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const agents = loadEffectiveConfig(rootDir).adapters.agents;
|
|
428
|
+
if (!isPlainObject(agents)) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
const models = agents.models;
|
|
432
|
+
if (!isPlainObject(models)) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
const model = models[agentFamily];
|
|
436
|
+
return typeof model === 'string' && model.length > 0 ? model : null;
|
|
480
437
|
}
|
|
481
|
-
|
|
482
438
|
function evaluateRepositoryReadiness(rootDir = process.cwd()) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
439
|
+
const explicit = loadWorkflowConfig(rootDir);
|
|
440
|
+
if (!explicit.found) {
|
|
441
|
+
return {
|
|
442
|
+
mode: 'default',
|
|
443
|
+
configPath: null,
|
|
444
|
+
issues: [],
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
if (explicit.parseError) {
|
|
448
|
+
return {
|
|
449
|
+
mode: 'invalid',
|
|
450
|
+
configPath: explicit.configPath,
|
|
451
|
+
issues: [`invalid JSON: ${explicit.parseError.message}`],
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
const issues = validateWorkflowConfig(explicit.config);
|
|
496
455
|
return {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
456
|
+
mode: issues.length === 0 ? 'configured' : 'invalid',
|
|
457
|
+
configPath: explicit.configPath,
|
|
458
|
+
issues,
|
|
500
459
|
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const issues = validateWorkflowConfig(explicit.config);
|
|
504
|
-
return {
|
|
505
|
-
mode: issues.length === 0 ? 'configured' : 'invalid',
|
|
506
|
-
configPath: explicit.configPath,
|
|
507
|
-
issues,
|
|
508
|
-
};
|
|
509
460
|
}
|
|
510
|
-
|
|
511
|
-
module.exports = {
|
|
512
|
-
REQUIRED_ADAPTER_KEYS,
|
|
513
|
-
DEFAULT_CONFIG,
|
|
514
|
-
defaultConfig,
|
|
515
|
-
adapterChecklist,
|
|
516
|
-
commitWorkflowBaseline,
|
|
517
|
-
configCandidates,
|
|
518
|
-
deepMerge,
|
|
519
|
-
detectLegacyRepoLayout,
|
|
520
|
-
evaluateRepositoryReadiness,
|
|
521
|
-
findWorkflowConfig,
|
|
522
|
-
hasGitRepository,
|
|
523
|
-
initializeGitRepository,
|
|
524
|
-
isForgejoReviewEnabled,
|
|
525
|
-
isStandaloneWorkflowLayout,
|
|
526
|
-
loadWorkflowConfig,
|
|
527
|
-
loadEffectiveConfig,
|
|
528
|
-
loadAdapterConfig,
|
|
529
|
-
resolveAgentAdapter,
|
|
530
|
-
resolveAgentModel,
|
|
531
|
-
resolveReviewAdapter,
|
|
532
|
-
resolveTaskStorage,
|
|
533
|
-
ensureStandaloneMissionBaseline,
|
|
534
|
-
ensureStandaloneGitRepo,
|
|
535
|
-
validateWorkflowConfig,
|
|
536
|
-
};
|