@lsctech/polaris 0.4.8 → 0.5.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/dist/autoresearch/proposal.js +70 -1
- package/dist/autoresearch/score.js +97 -0
- package/dist/cli/init.js +17 -0
- package/dist/cluster-state/store.js +35 -0
- package/dist/config/defaults.js +8 -0
- package/dist/config/validator.js +146 -0
- package/dist/loop/adapters/agent-subtask.js +19 -0
- package/dist/loop/adapters/terminal-cli.js +194 -11
- package/dist/loop/checkpoint.js +40 -0
- package/dist/loop/dispatch.js +193 -22
- package/dist/loop/orphan-recovery.js +56 -0
- package/dist/loop/parent.js +148 -21
- package/dist/loop/router/engine.js +229 -0
- package/dist/loop/router/index.js +5 -0
- package/dist/loop/router/types.js +2 -0
- package/dist/loop/worker-packet.js +16 -0
- package/dist/runtime/scheduling/child-selector.js +81 -0
- package/dist/smartdocs-engine/doctrine.js +101 -6
- package/dist/smartdocs-engine/ingest.js +18 -2
- package/dist/smartdocs-engine/migrate.js +1 -0
- package/package.json +1 -1
|
@@ -195,6 +195,10 @@ function compileStartupPacket(input) {
|
|
|
195
195
|
prompt_mode: 'full',
|
|
196
196
|
prompt_metrics: { mode: 'full', char_count: 0, estimated_tokens: 0 },
|
|
197
197
|
role_context: roleContextForWorkerRole('startup'),
|
|
198
|
+
routing_context: {
|
|
199
|
+
task_type: "startup",
|
|
200
|
+
required_capabilities: ["orchestration"],
|
|
201
|
+
},
|
|
198
202
|
result_file_contract: {
|
|
199
203
|
result_file: input.resultFile,
|
|
200
204
|
result_required_fields: Object.fromEntries([
|
|
@@ -287,6 +291,10 @@ function compileImplPacket(input) {
|
|
|
287
291
|
prompt_mode: promptMode,
|
|
288
292
|
prompt_metrics: promptResult.metrics,
|
|
289
293
|
role_context: roleContextForWorkerRole('impl'),
|
|
294
|
+
routing_context: {
|
|
295
|
+
task_type: "impl",
|
|
296
|
+
required_capabilities: ["implementation"],
|
|
297
|
+
},
|
|
290
298
|
prohibited_write_paths: exports.WORKER_PROHIBITED_WRITE_PATHS,
|
|
291
299
|
result_file_contract: {
|
|
292
300
|
result_file: input.resultFile,
|
|
@@ -341,6 +349,10 @@ function compileFinalizePacket(input) {
|
|
|
341
349
|
prompt_mode: 'full',
|
|
342
350
|
prompt_metrics: { mode: 'full', char_count: 0, estimated_tokens: 0 },
|
|
343
351
|
role_context: roleContextForWorkerRole('finalize'),
|
|
352
|
+
routing_context: {
|
|
353
|
+
task_type: "finalize",
|
|
354
|
+
required_capabilities: ["finalization"],
|
|
355
|
+
},
|
|
344
356
|
result_file_contract: {
|
|
345
357
|
result_file: input.resultFile,
|
|
346
358
|
result_required_fields: Object.fromEntries([
|
|
@@ -390,6 +402,10 @@ function compilePreflightPacket(input) {
|
|
|
390
402
|
prompt_mode: 'full',
|
|
391
403
|
prompt_metrics: { mode: 'full', char_count: 0, estimated_tokens: 0 },
|
|
392
404
|
role_context: roleContextForWorkerRole('preflight'),
|
|
405
|
+
routing_context: {
|
|
406
|
+
task_type: "startup",
|
|
407
|
+
required_capabilities: ["orchestration"],
|
|
408
|
+
},
|
|
393
409
|
result_file_contract: {
|
|
394
410
|
result_file: input.resultFile,
|
|
395
411
|
result_required_fields: Object.fromEntries([
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.selectNextChild = selectNextChild;
|
|
4
|
+
exports.selectChildSlotClaims = selectChildSlotClaims;
|
|
4
5
|
/**
|
|
5
6
|
* Select the next child to execute.
|
|
6
7
|
*
|
|
@@ -14,3 +15,83 @@ function selectNextChild(state) {
|
|
|
14
15
|
return null;
|
|
15
16
|
return [...state.open_children].sort()[0] ?? null;
|
|
16
17
|
}
|
|
18
|
+
function countActiveSlotsByProvider(claims) {
|
|
19
|
+
const activeSlotsByProvider = {};
|
|
20
|
+
for (const claim of claims) {
|
|
21
|
+
if (!claim.provider)
|
|
22
|
+
continue;
|
|
23
|
+
activeSlotsByProvider[claim.provider] = (activeSlotsByProvider[claim.provider] ?? 0) + 1;
|
|
24
|
+
}
|
|
25
|
+
return activeSlotsByProvider;
|
|
26
|
+
}
|
|
27
|
+
function selectChildSlotClaims(args) {
|
|
28
|
+
const now = args.now ?? new Date();
|
|
29
|
+
const nowMs = now.getTime();
|
|
30
|
+
const openSet = new Set(args.open_children);
|
|
31
|
+
const completedSet = new Set(args.completed_children);
|
|
32
|
+
const expired_claims = [];
|
|
33
|
+
const retainedClaims = args.existing_claims.filter((claim) => {
|
|
34
|
+
const expiresAt = new Date(claim.expires_at).getTime();
|
|
35
|
+
const notExpired = Number.isFinite(expiresAt) && expiresAt > nowMs;
|
|
36
|
+
const stillOpen = openSet.has(claim.child_id);
|
|
37
|
+
if (!notExpired || !stillOpen) {
|
|
38
|
+
expired_claims.push(claim.child_id);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
const rejected_children = {};
|
|
44
|
+
const claimedChildren = new Set(retainedClaims.map((claim) => claim.child_id));
|
|
45
|
+
const availableSlots = Math.max(0, args.max_concurrent - retainedClaims.length);
|
|
46
|
+
const newClaims = [];
|
|
47
|
+
if (availableSlots > 0) {
|
|
48
|
+
for (const childId of args.open_children) {
|
|
49
|
+
if (newClaims.length >= availableSlots)
|
|
50
|
+
break;
|
|
51
|
+
if (childId === args.active_child)
|
|
52
|
+
continue;
|
|
53
|
+
if (claimedChildren.has(childId))
|
|
54
|
+
continue;
|
|
55
|
+
const dependencies = args.get_dependencies(childId);
|
|
56
|
+
const unmetDependencies = dependencies.filter((dependency) => openSet.has(dependency) && !completedSet.has(dependency));
|
|
57
|
+
if (unmetDependencies.length > 0) {
|
|
58
|
+
rejected_children[childId] = "blocked-dependency";
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const decision = args.decide_route({
|
|
62
|
+
childId,
|
|
63
|
+
activeSlotsByProvider: countActiveSlotsByProvider([...retainedClaims, ...newClaims]),
|
|
64
|
+
});
|
|
65
|
+
const hardIneligibleReasons = new Set([
|
|
66
|
+
"role-disabled",
|
|
67
|
+
"not-in-policy",
|
|
68
|
+
"quota-exhausted",
|
|
69
|
+
"trust-too-low",
|
|
70
|
+
"capability-mismatch",
|
|
71
|
+
"cost-policy",
|
|
72
|
+
"no-slot",
|
|
73
|
+
]);
|
|
74
|
+
const routingExhausted = decision.exhaustedReason !== undefined &&
|
|
75
|
+
(hardIneligibleReasons.has(decision.exhaustedReason) || decision.mode !== "delegated");
|
|
76
|
+
const canClaim = decision.selectedProvider !== undefined || decision.mode === "delegated";
|
|
77
|
+
if (routingExhausted || !canClaim) {
|
|
78
|
+
rejected_children[childId] = "router-ineligible";
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
newClaims.push({
|
|
82
|
+
child_id: childId,
|
|
83
|
+
provider: decision.selectedProvider ?? null,
|
|
84
|
+
claimed_at: now.toISOString(),
|
|
85
|
+
expires_at: new Date(nowMs + args.claim_ttl_ms).toISOString(),
|
|
86
|
+
selection_reason: decision.selectionReason,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const slot_claims = [...retainedClaims, ...newClaims];
|
|
91
|
+
return {
|
|
92
|
+
selected_child: slot_claims[0]?.child_id ?? null,
|
|
93
|
+
slot_claims,
|
|
94
|
+
rejected_children,
|
|
95
|
+
expired_claims,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -4,6 +4,7 @@ exports.CANDIDATE_MARKER = void 0;
|
|
|
4
4
|
exports.parseFrontMatter = parseFrontMatter;
|
|
5
5
|
exports.addCandidateGovernanceMetadata = addCandidateGovernanceMetadata;
|
|
6
6
|
exports.stampIngestFrontMatter = stampIngestFrontMatter;
|
|
7
|
+
exports.deriveTypeFromDirectory = deriveTypeFromDirectory;
|
|
7
8
|
exports.backfillOkfType = backfillOkfType;
|
|
8
9
|
exports.doctrineDraft = doctrineDraft;
|
|
9
10
|
exports.doctrinePromote = doctrinePromote;
|
|
@@ -212,13 +213,102 @@ function addMissingFrontMatterField(content, key, value) {
|
|
|
212
213
|
}
|
|
213
214
|
return `---\n${key}: ${value}\n---\n\n${normalized}`;
|
|
214
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Sets a frontmatter field to a new value, replacing it if present or adding
|
|
218
|
+
* it if absent. Unlike `addMissingFrontMatterField`, this always applies the
|
|
219
|
+
* given value — used when a lifecycle transition (promote/deprecate) must
|
|
220
|
+
* make `type` agree with the doc's new directory tier, overriding whatever
|
|
221
|
+
* value it carried in its previous tier.
|
|
222
|
+
*/
|
|
223
|
+
function setFrontMatterField(content, key, value) {
|
|
224
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
225
|
+
if (normalized.startsWith("---\n")) {
|
|
226
|
+
const end = normalized.indexOf("\n---", 4);
|
|
227
|
+
if (end !== -1) {
|
|
228
|
+
const frontMatter = normalized.slice(4, end);
|
|
229
|
+
const afterFrontMatter = normalized.slice(end + 4);
|
|
230
|
+
const lowerKey = key.toLowerCase();
|
|
231
|
+
let found = false;
|
|
232
|
+
const newLines = frontMatter.split("\n").map((l) => {
|
|
233
|
+
const colonIdx = l.indexOf(":");
|
|
234
|
+
if (colonIdx !== -1 && l.slice(0, colonIdx).trim().toLowerCase() === lowerKey) {
|
|
235
|
+
found = true;
|
|
236
|
+
return `${key}: ${value}`;
|
|
237
|
+
}
|
|
238
|
+
return l;
|
|
239
|
+
});
|
|
240
|
+
if (found) {
|
|
241
|
+
return `---\n${newLines.join("\n")}\n---${afterFrontMatter}`;
|
|
242
|
+
}
|
|
243
|
+
return `---\n${frontMatter}\n${key}: ${value}\n---${afterFrontMatter}`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return `---\n${key}: ${value}\n---\n\n${normalized}`;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Directory-tier defaults for `type`, keyed by the first two path segments
|
|
250
|
+
* relative to `smartdocs/`. Mirrors the lifecycle-tier vocabulary already
|
|
251
|
+
* used by `ingest.ts`'s `DocsClassification`, since directory position — not
|
|
252
|
+
* frontmatter — is the authoritative signal for a doc's lifecycle state
|
|
253
|
+
* (docs-authority-model.md "Key rule"). Falling back to a literal `"raw"`
|
|
254
|
+
* for every file regardless of directory (the prior behavior) let a doc
|
|
255
|
+
* sitting in `doctrine/active/` end up with `type: raw`, contradicting its
|
|
256
|
+
* own directory-encoded authority tier.
|
|
257
|
+
*/
|
|
258
|
+
const DIRECTORY_TYPE_DEFAULTS = {
|
|
259
|
+
"raw": "raw",
|
|
260
|
+
"doctrine/active": "doctrine",
|
|
261
|
+
"doctrine/candidate": "doctrine-candidate",
|
|
262
|
+
"doctrine/deprecated": "doctrine-deprecated",
|
|
263
|
+
"doctrine": "doctrine",
|
|
264
|
+
"specs/raw": "raw",
|
|
265
|
+
"specs/active": "spec",
|
|
266
|
+
"specs/implemented": "spec-implemented",
|
|
267
|
+
"specs/superseded": "spec-superseded",
|
|
268
|
+
"specs/archive": "spec-archive",
|
|
269
|
+
"specs": "spec",
|
|
270
|
+
"audits/findings": "audit-finding",
|
|
271
|
+
"audits/resolved": "audit-resolved",
|
|
272
|
+
"audits": "audit",
|
|
273
|
+
"decisions": "decision",
|
|
274
|
+
"architecture": "architecture",
|
|
275
|
+
"integrations": "integration",
|
|
276
|
+
"medic": "medic",
|
|
277
|
+
"runtime/run-reports": "run-report",
|
|
278
|
+
"runtime/summaries": "runtime-summary",
|
|
279
|
+
"runtime": "runtime",
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* Derives a directory-tier-appropriate `type` default for a SmartDocs file
|
|
283
|
+
* that has neither `kind` nor `doc-type` set. `relPath` is relative to
|
|
284
|
+
* `smartdocs/` (forward-slash separated). Reserved front-door filenames
|
|
285
|
+
* (`POLARIS.md`, `SUMMARY.md`, `index.md`) get `"index"` regardless of tier,
|
|
286
|
+
* since they're navigational scaffolding, not tier content.
|
|
287
|
+
*/
|
|
288
|
+
function deriveTypeFromDirectory(relPath) {
|
|
289
|
+
const name = (0, node_path_1.basename)(relPath);
|
|
290
|
+
if (name === "POLARIS.md" || name === "SUMMARY.md" || name === "index.md") {
|
|
291
|
+
return "index";
|
|
292
|
+
}
|
|
293
|
+
const segments = relPath.split("/").slice(0, -1);
|
|
294
|
+
if (segments.length >= 2) {
|
|
295
|
+
const twoLevel = `${segments[0]}/${segments[1]}`;
|
|
296
|
+
if (DIRECTORY_TYPE_DEFAULTS[twoLevel])
|
|
297
|
+
return DIRECTORY_TYPE_DEFAULTS[twoLevel];
|
|
298
|
+
}
|
|
299
|
+
if (segments.length >= 1 && DIRECTORY_TYPE_DEFAULTS[segments[0]]) {
|
|
300
|
+
return DIRECTORY_TYPE_DEFAULTS[segments[0]];
|
|
301
|
+
}
|
|
302
|
+
return "raw";
|
|
303
|
+
}
|
|
215
304
|
/**
|
|
216
305
|
* Retrofit existing SmartDocs with an OKF-conformant `type` frontmatter field.
|
|
217
306
|
*
|
|
218
307
|
* Walks every `.md` file under `smartdocs/`. For each file already missing a
|
|
219
308
|
* `type` key: derives the value from `kind` if present, else `doc-type` if
|
|
220
|
-
* present, else
|
|
221
|
-
*
|
|
309
|
+
* present, else derives a directory-tier-appropriate default via
|
|
310
|
+
* `deriveTypeFromDirectory` (see that function for the tier mapping). Files
|
|
311
|
+
* that already declare `type` are left untouched and reported in `skipped`.
|
|
222
312
|
*
|
|
223
313
|
* This is the retroactive counterpart to `addCandidateGovernanceMetadata`,
|
|
224
314
|
* which stamps `type` on newly-governed candidate docs going forward — this
|
|
@@ -264,7 +354,8 @@ function backfillOkfType(repoRoot, options = {}) {
|
|
|
264
354
|
result.skipped.push(filePath);
|
|
265
355
|
return;
|
|
266
356
|
}
|
|
267
|
-
const
|
|
357
|
+
const relPath = (0, node_path_1.relative)(smartdocsDir, filePath).replace(/\\/g, "/");
|
|
358
|
+
const derivedType = fm.kind ?? fm["doc-type"] ?? deriveTypeFromDirectory(relPath);
|
|
268
359
|
if (!options.dryRun) {
|
|
269
360
|
(0, node_fs_1.writeFileSync)(filePath, addMissingFrontMatterField(content, "type", derivedType), "utf-8");
|
|
270
361
|
}
|
|
@@ -437,7 +528,8 @@ function doctrinePromote(path, options) {
|
|
|
437
528
|
if ((0, node_fs_1.existsSync)(destination)) {
|
|
438
529
|
throw new Error(`Destination already exists: ${destination}`);
|
|
439
530
|
}
|
|
440
|
-
const
|
|
531
|
+
const strippedContent = content.replace(`${exports.CANDIDATE_MARKER}\n`, "").replace(exports.CANDIDATE_MARKER, "");
|
|
532
|
+
const activeContent = setFrontMatterField(strippedContent, "type", "doctrine");
|
|
441
533
|
(0, node_fs_1.writeFileSync)(destination, activeContent, "utf-8");
|
|
442
534
|
(0, node_fs_1.unlinkSync)(source);
|
|
443
535
|
// Move co-located provenance sidecar if present
|
|
@@ -501,7 +593,8 @@ function doctrineDeprecate(path, options) {
|
|
|
501
593
|
}
|
|
502
594
|
const content = (0, node_fs_1.readFileSync)(source, "utf-8");
|
|
503
595
|
const deprecatedAt = new Date().toISOString();
|
|
504
|
-
const
|
|
596
|
+
const retypedContent = setFrontMatterField(content, "type", "doctrine-deprecated");
|
|
597
|
+
const deprecatedContent = `<!-- polaris:doctrine-deprecated deprecatedAt="${deprecatedAt}" runId="${runId}" -->\n${retypedContent}`;
|
|
505
598
|
(0, node_fs_1.writeFileSync)(destination, deprecatedContent, "utf-8");
|
|
506
599
|
(0, node_fs_1.unlinkSync)(source);
|
|
507
600
|
// Move co-located provenance sidecar if present
|
|
@@ -834,7 +927,9 @@ function specPromote(path, options) {
|
|
|
834
927
|
if ((0, node_fs_1.existsSync)(destination)) {
|
|
835
928
|
throw new Error(`Destination already exists: ${destination}`);
|
|
836
929
|
}
|
|
837
|
-
(
|
|
930
|
+
const retypedContent = setFrontMatterField(content, "type", "spec");
|
|
931
|
+
(0, node_fs_1.writeFileSync)(destination, retypedContent, "utf-8");
|
|
932
|
+
(0, node_fs_1.unlinkSync)(source);
|
|
838
933
|
if ((0, node_fs_1.existsSync)(provenanceSrcPath)) {
|
|
839
934
|
(0, node_fs_1.renameSync)(provenanceSrcPath, destination.replace(/\.md$/, ".provenance.json"));
|
|
840
935
|
}
|
|
@@ -456,8 +456,14 @@ function ingestDocs(files, options) {
|
|
|
456
456
|
};
|
|
457
457
|
const routingDecision = (0, index_js_1.route)(classificationResult, thresholds);
|
|
458
458
|
const proposedDest = (0, node_path_1.relative)(repoRoot, (0, node_path_1.join)((0, node_path_1.resolve)(repoRoot, TARGET_DIRS[docClassification]), (0, node_path_1.basename)(absSource))).replace(/\\/g, "/");
|
|
459
|
+
// A prior human/agent review decision or a scoped --approve-authority call
|
|
460
|
+
// overrides an otherwise-review-required outcome so the placement it
|
|
461
|
+
// decided on actually happens.
|
|
462
|
+
const priorDecision = priorQueue.find((p) => p.sourcePath === relSource)?.reviewDecision;
|
|
463
|
+
const forcedApprove = routingDecision.outcome === "review-required" &&
|
|
464
|
+
(priorDecision === "approve" || Boolean(options.approveAuthority));
|
|
459
465
|
// review-required: leave in raw/, emit packet, skip move
|
|
460
|
-
if (routingDecision.outcome === "review-required") {
|
|
466
|
+
if (routingDecision.outcome === "review-required" && !forcedApprove) {
|
|
461
467
|
const packet = {
|
|
462
468
|
...routingDecision.reviewPacket,
|
|
463
469
|
sourcePath: relSource,
|
|
@@ -504,6 +510,16 @@ function ingestDocs(files, options) {
|
|
|
504
510
|
linked_map_area: linkedMapArea,
|
|
505
511
|
cluster_id: clusterId,
|
|
506
512
|
});
|
|
513
|
+
if (forcedApprove) {
|
|
514
|
+
emitTelemetry(telPath, runId, {
|
|
515
|
+
event: "docs-ingest-approved-override",
|
|
516
|
+
file: relSource,
|
|
517
|
+
classification: docClassification,
|
|
518
|
+
destination: relDestination,
|
|
519
|
+
reason: priorDecision === "approve" ? "prior-review-decision" : "approve-authority-flag",
|
|
520
|
+
cluster_id: clusterId,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
507
523
|
if (!options.dryRun) {
|
|
508
524
|
if ((0, node_path_1.resolve)(absSource) !== (0, node_path_1.resolve)(destination)) {
|
|
509
525
|
(0, node_fs_1.renameSync)(absSource, destination);
|
|
@@ -583,7 +599,7 @@ function ingestDocs(files, options) {
|
|
|
583
599
|
dryRun: Boolean(options.dryRun),
|
|
584
600
|
nearestSummary,
|
|
585
601
|
summaryDeltaWarranted: summaryDelta.updateWarranted,
|
|
586
|
-
routingDecision: routingDecision.outcome,
|
|
602
|
+
routingDecision: forcedApprove ? "approved-override" : routingDecision.outcome,
|
|
587
603
|
reviewPacket: routingDecision.reviewPacket
|
|
588
604
|
? { ...routingDecision.reviewPacket, sourcePath: relSource, proposedDestination: relDestination, conflicts: [] }
|
|
589
605
|
: undefined,
|