job-forge 2.14.33 → 2.14.35

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.
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { relative, resolve } from 'path';
4
+ import {
5
+ formatCheckResult,
6
+ formatExplainGraph,
7
+ formatRecordResult,
8
+ formatStaleResult,
9
+ formatVerifyResult,
10
+ parseJson,
11
+ } from '@razroo/iso-lineage';
12
+ import { PROJECT_DIR } from '../tracker-lib.mjs';
13
+ import {
14
+ checkJobForgeLineage,
15
+ jobForgeLineagePath,
16
+ jobForgeLineageSummary,
17
+ lineageExists,
18
+ normalizeJobForgeLineageArtifact,
19
+ readJobForgeLineage,
20
+ readJobForgeLineageOrEmpty,
21
+ recordJobForgeLineage,
22
+ staleJobForgeLineage,
23
+ verifyJobForgeLineage,
24
+ } from '../lib/jobforge-lineage.mjs';
25
+
26
+ const USAGE = `job-forge lineage - deterministic artifact lineage and stale-output checks
27
+
28
+ Usage:
29
+ job-forge lineage:status [--json]
30
+ job-forge lineage:record --artifact <file> [--input <file>...] [--optional-input <file>...] [--kind <kind>] [--command <cmd>] [--metadata <json>] [--now <iso>] [--json]
31
+ job-forge lineage:check [--artifact <file>] [--json]
32
+ job-forge lineage:stale [--json]
33
+ job-forge lineage:verify [--json]
34
+ job-forge lineage:explain [--artifact <file>] [--json]
35
+ job-forge lineage:path
36
+
37
+ Default graph is .jobforge-lineage.json. Record generated reports, PDFs, and
38
+ other derived artifacts against their source CV/profile/report inputs, then
39
+ check whether outputs are stale after inputs change.`;
40
+
41
+ const [cmd = 'help', ...rawArgs] = process.argv.slice(2);
42
+ const opts = parseArgs(rawArgs);
43
+
44
+ if (opts.help || cmd === 'help' || cmd === '--help' || cmd === '-h') {
45
+ console.log(USAGE);
46
+ process.exit(0);
47
+ }
48
+
49
+ try {
50
+ if (cmd === 'path') {
51
+ console.log(jobForgeLineagePath(PROJECT_DIR));
52
+ } else if (cmd === 'status') {
53
+ status(opts);
54
+ } else if (cmd === 'record') {
55
+ record(opts);
56
+ } else if (cmd === 'check') {
57
+ check(opts);
58
+ } else if (cmd === 'stale') {
59
+ stale(opts);
60
+ } else if (cmd === 'verify') {
61
+ verify(opts);
62
+ } else if (cmd === 'explain') {
63
+ explain(opts);
64
+ } else {
65
+ console.error(`unknown lineage command "${cmd}"\n`);
66
+ console.error(USAGE);
67
+ process.exit(2);
68
+ }
69
+ } catch (error) {
70
+ console.error(error instanceof Error ? error.message : String(error));
71
+ process.exit(1);
72
+ }
73
+
74
+ function parseArgs(args) {
75
+ const opts = {
76
+ json: false,
77
+ help: false,
78
+ inputs: [],
79
+ optionalInputs: [],
80
+ };
81
+
82
+ for (let i = 0; i < args.length; i++) {
83
+ const arg = args[i];
84
+ if (arg === '--json') {
85
+ opts.json = true;
86
+ } else if (arg === '--artifact') {
87
+ opts.artifact = valueAfter(args, ++i, '--artifact');
88
+ } else if (arg.startsWith('--artifact=')) {
89
+ opts.artifact = arg.slice('--artifact='.length);
90
+ } else if (arg === '--input') {
91
+ opts.inputs.push(valueAfter(args, ++i, '--input'));
92
+ } else if (arg.startsWith('--input=')) {
93
+ opts.inputs.push(arg.slice('--input='.length));
94
+ } else if (arg === '--optional-input') {
95
+ opts.optionalInputs.push(valueAfter(args, ++i, '--optional-input'));
96
+ } else if (arg.startsWith('--optional-input=')) {
97
+ opts.optionalInputs.push(arg.slice('--optional-input='.length));
98
+ } else if (arg === '--kind') {
99
+ opts.kind = valueAfter(args, ++i, '--kind');
100
+ } else if (arg.startsWith('--kind=')) {
101
+ opts.kind = arg.slice('--kind='.length);
102
+ } else if (arg === '--command') {
103
+ opts.command = valueAfter(args, ++i, '--command');
104
+ } else if (arg.startsWith('--command=')) {
105
+ opts.command = arg.slice('--command='.length);
106
+ } else if (arg === '--metadata') {
107
+ opts.metadata = parseMetadata(valueAfter(args, ++i, '--metadata'));
108
+ } else if (arg.startsWith('--metadata=')) {
109
+ opts.metadata = parseMetadata(arg.slice('--metadata='.length));
110
+ } else if (arg === '--now') {
111
+ opts.now = valueAfter(args, ++i, '--now');
112
+ } else if (arg.startsWith('--now=')) {
113
+ opts.now = arg.slice('--now='.length);
114
+ } else if (arg === '--out') {
115
+ opts.out = resolve(PROJECT_DIR, valueAfter(args, ++i, '--out'));
116
+ } else if (arg.startsWith('--out=')) {
117
+ opts.out = resolve(PROJECT_DIR, arg.slice('--out='.length));
118
+ } else if (arg === '--help' || arg === '-h') {
119
+ opts.help = true;
120
+ } else {
121
+ throw new Error(`unknown flag "${arg}"`);
122
+ }
123
+ }
124
+
125
+ return opts;
126
+ }
127
+
128
+ function valueAfter(values, index, flag) {
129
+ const value = values[index];
130
+ if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
131
+ return value;
132
+ }
133
+
134
+ function parseMetadata(value) {
135
+ const parsed = parseJson(value, '--metadata');
136
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
137
+ throw new Error('--metadata must be a JSON object');
138
+ }
139
+ return parsed;
140
+ }
141
+
142
+ function status(opts) {
143
+ const summary = jobForgeLineageSummary(PROJECT_DIR);
144
+ if (opts.json) {
145
+ console.log(JSON.stringify(summary, null, 2));
146
+ return;
147
+ }
148
+ if (!summary.exists) {
149
+ console.log(`lineage: missing (${relativePath(summary.path)})`);
150
+ console.log('run: job-forge lineage:record --artifact <file> --input <file>');
151
+ return;
152
+ }
153
+ console.log(`lineage: ${relativePath(summary.path)}`);
154
+ console.log(`records: ${summary.records}`);
155
+ console.log(`current: ${summary.current}`);
156
+ console.log(`stale: ${summary.stale}`);
157
+ console.log(`missing: ${summary.missing}`);
158
+ console.log(`check: ${summary.ok ? 'PASS' : 'STALE'}`);
159
+ }
160
+
161
+ function record(opts) {
162
+ const result = recordJobForgeLineage({
163
+ artifact: opts.artifact,
164
+ inputs: opts.inputs,
165
+ optionalInputs: opts.optionalInputs,
166
+ kind: opts.kind,
167
+ command: opts.command,
168
+ metadata: opts.metadata,
169
+ now: opts.now,
170
+ out: opts.out,
171
+ }, PROJECT_DIR);
172
+ if (opts.json) {
173
+ console.log(JSON.stringify(result, null, 2));
174
+ return;
175
+ }
176
+ console.log(formatRecordResult(result.graph, result.record, relativePath(result.out)));
177
+ }
178
+
179
+ function check(opts) {
180
+ if (!lineageExists(PROJECT_DIR)) {
181
+ if (opts.json) {
182
+ console.log(JSON.stringify({ ok: true, missing: true, path: jobForgeLineagePath(PROJECT_DIR) }, null, 2));
183
+ } else {
184
+ console.log(`lineage: missing (${relativePath(jobForgeLineagePath(PROJECT_DIR))})`);
185
+ }
186
+ return;
187
+ }
188
+ const result = checkJobForgeLineage({ artifact: opts.artifact }, PROJECT_DIR);
189
+ if (opts.json) {
190
+ console.log(JSON.stringify(result, null, 2));
191
+ } else {
192
+ console.log(formatCheckResult(result));
193
+ }
194
+ process.exit(result.ok ? 0 : 1);
195
+ }
196
+
197
+ function stale(opts) {
198
+ if (!lineageExists(PROJECT_DIR)) {
199
+ if (opts.json) {
200
+ console.log(JSON.stringify({ ok: true, missing: true, path: jobForgeLineagePath(PROJECT_DIR) }, null, 2));
201
+ } else {
202
+ console.log(`lineage: missing (${relativePath(jobForgeLineagePath(PROJECT_DIR))})`);
203
+ }
204
+ return;
205
+ }
206
+ const result = staleJobForgeLineage({}, PROJECT_DIR);
207
+ if (opts.json) {
208
+ console.log(JSON.stringify(result, null, 2));
209
+ } else {
210
+ console.log(formatStaleResult(result));
211
+ }
212
+ }
213
+
214
+ function verify(opts) {
215
+ if (!lineageExists(PROJECT_DIR)) {
216
+ if (opts.json) {
217
+ console.log(JSON.stringify({ ok: true, missing: true, path: jobForgeLineagePath(PROJECT_DIR) }, null, 2));
218
+ } else {
219
+ console.log(`lineage: missing (${relativePath(jobForgeLineagePath(PROJECT_DIR))})`);
220
+ }
221
+ return;
222
+ }
223
+ const result = verifyJobForgeLineage({}, PROJECT_DIR);
224
+ if (opts.json) {
225
+ console.log(JSON.stringify(result, null, 2));
226
+ } else {
227
+ console.log(formatVerifyResult(result));
228
+ }
229
+ process.exit(result.ok ? 0 : 1);
230
+ }
231
+
232
+ function explain(opts) {
233
+ const graph = lineageExists(PROJECT_DIR) ? readJobForgeLineage(PROJECT_DIR) : readJobForgeLineageOrEmpty(PROJECT_DIR);
234
+ const artifact = opts.artifact
235
+ ? normalizeJobForgeLineageArtifact(PROJECT_DIR, opts.artifact)
236
+ : undefined;
237
+ if (opts.json) {
238
+ const records = artifact ? graph.records.filter((record) => record.artifact.path === artifact) : graph.records;
239
+ console.log(JSON.stringify({ ...graph, records }, null, 2));
240
+ return;
241
+ }
242
+ console.log(formatExplainGraph(graph, artifact));
243
+ }
244
+
245
+ function relativePath(path) {
246
+ return relative(PROJECT_DIR, path) || '.';
247
+ }
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from 'fs';
4
+ import { isAbsolute, relative, resolve } from 'path';
5
+ import {
6
+ formatCheckResult,
7
+ formatConfigSummary,
8
+ formatPrioritizeResult,
9
+ formatVerifyResult,
10
+ loadPrioritizeItems,
11
+ parseJson,
12
+ } from '@razroo/iso-prioritize';
13
+ import { PROJECT_DIR } from '../tracker-lib.mjs';
14
+ import {
15
+ buildJobForgePrioritizeItems,
16
+ checkJobForgePrioritize,
17
+ jobForgePrioritizeConfigPath,
18
+ jobForgePrioritizeItemsPath,
19
+ jobForgePrioritizePath,
20
+ jobForgePrioritizeSummary,
21
+ prioritizeExists,
22
+ rankJobForgePrioritize,
23
+ readJobForgePrioritizeConfig,
24
+ selectJobForgePrioritize,
25
+ verifyJobForgePrioritize,
26
+ writeJobForgePrioritize,
27
+ writeJobForgePrioritizeItems,
28
+ } from '../lib/jobforge-prioritize.mjs';
29
+
30
+ const USAGE = `job-forge prioritize - deterministic next-action ranking
31
+
32
+ Usage:
33
+ job-forge prioritize:status [--json]
34
+ job-forge prioritize:items [--now <iso>] [--out <file>] [--json]
35
+ job-forge prioritize:build [--now <iso>] [--profile <name>] [--limit N] [--out <file>] [--items-out <file>] [--json]
36
+ job-forge prioritize:rank [--items <file>] [--profile <name>] [--limit N] [--json]
37
+ job-forge prioritize:select [--items <file>] [--profile <name>] [--limit N] [--json]
38
+ job-forge prioritize:check [--items <file>] [--profile <name>] [--limit N] [--min-selected N] [--fail-on blocked|skipped,blocked|none] [--json]
39
+ job-forge prioritize:verify [--json]
40
+ job-forge prioritize:explain [--profile <name>] [--json]
41
+ job-forge prioritize:path [--config|--items]
42
+
43
+ Default policy is templates/prioritize.json. The generated queue is local
44
+ project state (.jobforge-prioritize.json), derived from source-backed facts and
45
+ due timeline items.`;
46
+
47
+ const [cmd = 'help', ...rawArgs] = process.argv.slice(2);
48
+ const opts = parseArgs(rawArgs);
49
+
50
+ if (opts.help || cmd === 'help' || cmd === '--help' || cmd === '-h') {
51
+ console.log(USAGE);
52
+ process.exit(0);
53
+ }
54
+
55
+ try {
56
+ if (cmd === 'path') {
57
+ path(opts);
58
+ } else if (cmd === 'status') {
59
+ status(opts);
60
+ } else if (cmd === 'items') {
61
+ items(opts);
62
+ } else if (cmd === 'build') {
63
+ build(opts);
64
+ } else if (cmd === 'rank') {
65
+ rank(opts);
66
+ } else if (cmd === 'select') {
67
+ select(opts);
68
+ } else if (cmd === 'check') {
69
+ check(opts);
70
+ } else if (cmd === 'verify') {
71
+ verify(opts);
72
+ } else if (cmd === 'explain') {
73
+ explain(opts);
74
+ } else {
75
+ console.error(`unknown prioritize command "${cmd}"\n`);
76
+ console.error(USAGE);
77
+ process.exit(2);
78
+ }
79
+ } catch (error) {
80
+ console.error(error instanceof Error ? error.message : String(error));
81
+ process.exit(1);
82
+ }
83
+
84
+ function parseArgs(args) {
85
+ const opts = {
86
+ json: false,
87
+ help: false,
88
+ rebuild: true,
89
+ };
90
+
91
+ for (let i = 0; i < args.length; i++) {
92
+ const arg = args[i];
93
+ if (arg === '--json') {
94
+ opts.json = true;
95
+ } else if (arg === '--no-rebuild') {
96
+ opts.rebuild = false;
97
+ } else if (arg === '--profile') {
98
+ opts.profile = valueAfter(args, ++i, '--profile');
99
+ } else if (arg.startsWith('--profile=')) {
100
+ opts.profile = arg.slice('--profile='.length);
101
+ } else if (arg === '--limit') {
102
+ opts.limit = parsePositiveInteger(valueAfter(args, ++i, '--limit'), '--limit');
103
+ } else if (arg.startsWith('--limit=')) {
104
+ opts.limit = parsePositiveInteger(arg.slice('--limit='.length), '--limit');
105
+ } else if (arg === '--min-selected') {
106
+ opts.minSelected = parseNonNegativeInteger(valueAfter(args, ++i, '--min-selected'), '--min-selected');
107
+ } else if (arg.startsWith('--min-selected=')) {
108
+ opts.minSelected = parseNonNegativeInteger(arg.slice('--min-selected='.length), '--min-selected');
109
+ } else if (arg === '--fail-on') {
110
+ opts.failOn = parseFailOn(valueAfter(args, ++i, '--fail-on'));
111
+ } else if (arg.startsWith('--fail-on=')) {
112
+ opts.failOn = parseFailOn(arg.slice('--fail-on='.length));
113
+ } else if (arg === '--now') {
114
+ opts.now = valueAfter(args, ++i, '--now');
115
+ } else if (arg.startsWith('--now=')) {
116
+ opts.now = arg.slice('--now='.length);
117
+ } else if (arg === '--items') {
118
+ if (!args[i + 1] || args[i + 1].startsWith('--')) {
119
+ opts.itemsPath = true;
120
+ } else {
121
+ opts.items = valueAfter(args, ++i, '--items');
122
+ }
123
+ } else if (arg.startsWith('--items=')) {
124
+ opts.items = arg.slice('--items='.length);
125
+ } else if (arg === '--out') {
126
+ opts.out = resolveInputPath(valueAfter(args, ++i, '--out'));
127
+ } else if (arg.startsWith('--out=')) {
128
+ opts.out = resolveInputPath(arg.slice('--out='.length));
129
+ } else if (arg === '--items-out') {
130
+ opts.itemsOut = resolveInputPath(valueAfter(args, ++i, '--items-out'));
131
+ } else if (arg.startsWith('--items-out=')) {
132
+ opts.itemsOut = resolveInputPath(arg.slice('--items-out='.length));
133
+ } else if (arg === '--config') {
134
+ opts.configPath = true;
135
+ } else if (arg === '--help' || arg === '-h') {
136
+ opts.help = true;
137
+ } else {
138
+ throw new Error(`unknown flag "${arg}"`);
139
+ }
140
+ }
141
+
142
+ return opts;
143
+ }
144
+
145
+ function valueAfter(values, index, flag) {
146
+ const value = values[index];
147
+ if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
148
+ return value;
149
+ }
150
+
151
+ function parsePositiveInteger(value, flag) {
152
+ const parsed = Number(value);
153
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${flag} must be a positive integer`);
154
+ return parsed;
155
+ }
156
+
157
+ function parseNonNegativeInteger(value, flag) {
158
+ const parsed = Number(value);
159
+ if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${flag} must be a non-negative integer`);
160
+ return parsed;
161
+ }
162
+
163
+ function parseFailOn(value) {
164
+ if (value === 'none') return 'none';
165
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
166
+ }
167
+
168
+ function path(opts) {
169
+ if (opts.configPath) {
170
+ console.log(jobForgePrioritizeConfigPath(PROJECT_DIR));
171
+ } else if (opts.itemsPath) {
172
+ console.log(jobForgePrioritizeItemsPath(PROJECT_DIR));
173
+ } else {
174
+ console.log(jobForgePrioritizePath(PROJECT_DIR));
175
+ }
176
+ }
177
+
178
+ function status(opts) {
179
+ const summary = jobForgePrioritizeSummary(PROJECT_DIR);
180
+ if (opts.json) {
181
+ console.log(JSON.stringify(summary, null, 2));
182
+ return;
183
+ }
184
+ if (!summary.exists) {
185
+ console.log(`prioritize: missing (${relativePath(summary.path)})`);
186
+ console.log('run: job-forge prioritize:build');
187
+ return;
188
+ }
189
+ const result = verifyJobForgePrioritize({}, PROJECT_DIR);
190
+ console.log(`prioritize: ${relativePath(summary.path)}`);
191
+ console.log(`items: ${relativePath(summary.itemsPath)}`);
192
+ console.log(`profile: ${summary.profile}`);
193
+ console.log(`total: ${summary.items}`);
194
+ console.log(`selected: ${summary.selected}`);
195
+ console.log(`candidate: ${summary.candidate}`);
196
+ console.log(`skipped: ${summary.skipped}`);
197
+ console.log(`blocked: ${summary.blocked}`);
198
+ console.log(`verify: ${result.ok ? 'PASS' : 'FAIL'} (${result.errors} errors, ${result.warnings} warnings)`);
199
+ }
200
+
201
+ function items(opts) {
202
+ const input = buildJobForgePrioritizeItems({ now: opts.now, rebuild: opts.rebuild }, PROJECT_DIR);
203
+ if (opts.out) writeJobForgePrioritizeItems(input, { out: opts.out }, PROJECT_DIR);
204
+ if (opts.json) {
205
+ console.log(JSON.stringify(input, null, 2));
206
+ return;
207
+ }
208
+ if (opts.out) console.log(`items: wrote ${relativePath(opts.out)}`);
209
+ console.log(`items: ${input.items.length}`);
210
+ }
211
+
212
+ function build(opts) {
213
+ const input = readItems(opts) || buildJobForgePrioritizeItems({ now: opts.now, rebuild: opts.rebuild }, PROJECT_DIR);
214
+ const result = rankJobForgePrioritize({
215
+ items: input,
216
+ profile: opts.profile,
217
+ limit: opts.limit,
218
+ }, PROJECT_DIR);
219
+ const itemsOut = writeJobForgePrioritizeItems(input, { out: opts.itemsOut }, PROJECT_DIR);
220
+ const out = writeJobForgePrioritize(result, { out: opts.out }, PROJECT_DIR);
221
+ if (opts.json) {
222
+ console.log(JSON.stringify({ out, itemsOut, result }, null, 2));
223
+ return;
224
+ }
225
+ console.log(`prioritize: wrote ${relativePath(out)}`);
226
+ console.log(`items: wrote ${relativePath(itemsOut)}`);
227
+ console.log(formatPrioritizeResult(result));
228
+ }
229
+
230
+ function rank(opts) {
231
+ const result = rankJobForgePrioritize({
232
+ items: readItems(opts),
233
+ profile: opts.profile,
234
+ limit: opts.limit,
235
+ now: opts.now,
236
+ rebuild: opts.rebuild,
237
+ }, PROJECT_DIR);
238
+ if (opts.out) writeJobForgePrioritize(result, { out: opts.out }, PROJECT_DIR);
239
+ if (opts.json) {
240
+ console.log(JSON.stringify(result, null, 2));
241
+ return;
242
+ }
243
+ console.log(formatPrioritizeResult(result));
244
+ }
245
+
246
+ function select(opts) {
247
+ const result = selectJobForgePrioritize({
248
+ items: readItems(opts),
249
+ profile: opts.profile,
250
+ limit: opts.limit,
251
+ now: opts.now,
252
+ rebuild: opts.rebuild,
253
+ }, PROJECT_DIR);
254
+ if (opts.out) writeJobForgePrioritize(result, { out: opts.out }, PROJECT_DIR);
255
+ if (opts.json) {
256
+ console.log(JSON.stringify(result, null, 2));
257
+ return;
258
+ }
259
+ console.log(formatPrioritizeResult(result));
260
+ }
261
+
262
+ function check(opts) {
263
+ const result = checkJobForgePrioritize({
264
+ items: readItems(opts),
265
+ profile: opts.profile,
266
+ limit: opts.limit,
267
+ minSelected: opts.minSelected,
268
+ failOn: opts.failOn,
269
+ now: opts.now,
270
+ rebuild: opts.rebuild,
271
+ }, PROJECT_DIR);
272
+ if (opts.json) {
273
+ console.log(JSON.stringify(result, null, 2));
274
+ } else {
275
+ console.log(formatCheckResult(result));
276
+ }
277
+ process.exit(result.ok ? 0 : 1);
278
+ }
279
+
280
+ function verify(opts) {
281
+ if (!prioritizeExists(PROJECT_DIR)) {
282
+ if (opts.json) {
283
+ console.log(JSON.stringify({ ok: true, missing: true, path: jobForgePrioritizePath(PROJECT_DIR) }, null, 2));
284
+ } else {
285
+ console.log(`prioritize: missing (${relativePath(jobForgePrioritizePath(PROJECT_DIR))})`);
286
+ }
287
+ return;
288
+ }
289
+ const result = verifyJobForgePrioritize({}, PROJECT_DIR);
290
+ if (opts.json) {
291
+ console.log(JSON.stringify(result, null, 2));
292
+ } else {
293
+ console.log(formatVerifyResult(result));
294
+ }
295
+ process.exit(result.ok ? 0 : 1);
296
+ }
297
+
298
+ function explain(opts) {
299
+ const config = readJobForgePrioritizeConfig(PROJECT_DIR);
300
+ if (opts.json) {
301
+ const value = opts.profile
302
+ ? { ...config, profiles: config.profiles.filter((profile) => profile.name === opts.profile) }
303
+ : config;
304
+ console.log(JSON.stringify(value, null, 2));
305
+ return;
306
+ }
307
+ console.log(`config: ${relativePath(jobForgePrioritizeConfigPath(PROJECT_DIR))}`);
308
+ console.log(formatConfigSummary(config, opts.profile));
309
+ }
310
+
311
+ function readItems(opts) {
312
+ if (!opts.items) return undefined;
313
+ const path = resolveInputPath(opts.items);
314
+ return { items: loadPrioritizeItems(parseJson(readFileSync(path, 'utf8'), path)) };
315
+ }
316
+
317
+ function resolveInputPath(path) {
318
+ return isAbsolute(path) ? path : resolve(PROJECT_DIR, path);
319
+ }
320
+
321
+ function relativePath(path) {
322
+ return relative(PROJECT_DIR, path) || '.';
323
+ }
@@ -63,6 +63,20 @@
63
63
  "timeline:check": "job-forge timeline:check",
64
64
  "timeline:verify": "job-forge timeline:verify",
65
65
  "timeline:explain": "job-forge timeline:explain",
66
+ "prioritize:status": "job-forge prioritize:status",
67
+ "prioritize:items": "job-forge prioritize:items",
68
+ "prioritize:build": "job-forge prioritize:build",
69
+ "prioritize:rank": "job-forge prioritize:rank",
70
+ "prioritize:select": "job-forge prioritize:select",
71
+ "prioritize:check": "job-forge prioritize:check",
72
+ "prioritize:verify": "job-forge prioritize:verify",
73
+ "prioritize:explain": "job-forge prioritize:explain",
74
+ "lineage:status": "job-forge lineage:status",
75
+ "lineage:record": "job-forge lineage:record",
76
+ "lineage:check": "job-forge lineage:check",
77
+ "lineage:stale": "job-forge lineage:stale",
78
+ "lineage:verify": "job-forge lineage:verify",
79
+ "lineage:explain": "job-forge lineage:explain",
66
80
  "redact:scan": "job-forge redact:scan",
67
81
  "redact:verify": "job-forge redact:verify",
68
82
  "redact:apply": "job-forge redact:apply",
@@ -92,6 +106,9 @@
92
106
  ".jobforge-facts.json",
93
107
  ".jobforge-timeline.json",
94
108
  ".jobforge-timeline-events.jsonl",
109
+ ".jobforge-prioritize.json",
110
+ ".jobforge-prioritize-items.json",
111
+ ".jobforge-lineage.json",
95
112
  ".jobforge-redacted/",
96
113
  "data/timeline-events.jsonl",
97
114
  "batch/preflight-candidates.json",