@kontourai/flow-agents 3.3.0 → 3.4.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.
- package/.github/workflows/add-to-project.yml +15 -0
- package/.github/workflows/ci.yml +161 -0
- package/CHANGELOG.md +48 -0
- package/CONTEXT.md +5 -1
- package/README.md +19 -8
- package/build/src/builder-flow-run-adapter.d.ts +80 -0
- package/build/src/builder-flow-run-adapter.js +241 -0
- package/build/src/builder-flow-runtime.d.ts +16 -0
- package/build/src/builder-flow-runtime.js +290 -0
- package/build/src/cli/builder-run.d.ts +1 -0
- package/build/src/cli/builder-run.js +27 -0
- package/build/src/cli/effective-backlog-settings.js +70 -2
- package/build/src/cli/init.d.ts +34 -0
- package/build/src/cli/init.js +341 -61
- package/build/src/cli/kit.js +55 -12
- package/build/src/cli/pull-work-provider.js +346 -5
- package/build/src/cli/skill-drift-check.d.ts +1 -0
- package/build/src/cli/skill-drift-check.js +165 -0
- package/build/src/cli/telemetry-doctor.d.ts +37 -0
- package/build/src/cli/telemetry-doctor.js +53 -6
- package/build/src/cli/validate-hook-influence.js +37 -7
- package/build/src/cli/workflow-sidecar.d.ts +93 -8
- package/build/src/cli/workflow-sidecar.js +1175 -158
- package/build/src/cli.js +5 -0
- package/build/src/flow-kit/validate.d.ts +54 -34
- package/build/src/flow-kit/validate.js +237 -26
- package/build/src/index.d.ts +2 -0
- package/build/src/index.js +1 -0
- package/build/src/lib/console-connect-options.d.ts +97 -0
- package/build/src/lib/console-connect-options.js +199 -0
- package/build/src/lib/console-telemetry-validate.d.ts +49 -0
- package/build/src/lib/console-telemetry-validate.js +91 -0
- package/build/src/lib/flow-resolver.d.ts +56 -3
- package/build/src/lib/flow-resolver.js +151 -11
- package/build/src/lib/fs.d.ts +17 -0
- package/build/src/lib/fs.js +172 -0
- package/build/src/lib/local-artifact-root.d.ts +44 -1
- package/build/src/lib/local-artifact-root.js +131 -3
- package/build/src/runtime-adapters.d.ts +39 -3
- package/build/src/runtime-adapters.js +77 -31
- package/build/src/tools/build-universal-bundles.js +40 -2
- package/build/src/tools/codex-agent-routing.d.ts +2 -0
- package/build/src/tools/codex-agent-routing.js +49 -0
- package/build/src/tools/generate-context-map.js +1 -0
- package/build/src/tools/validate-source-tree.js +27 -1
- package/context/scripts/hooks/lib/kit-catalog.js +235 -0
- package/context/scripts/hooks/lib/runnable-command.js +177 -0
- package/context/scripts/hooks/stop-goal-fit.js +278 -48
- package/context/scripts/hooks/workflow-steering.js +121 -21
- package/context/scripts/package.json +3 -0
- package/context/scripts/telemetry/install-console-config.sh +25 -4
- package/context/scripts/telemetry/lib/config.sh +102 -12
- package/context/scripts/telemetry/lib/pricing.sh +50 -0
- package/context/scripts/telemetry/lib/session.sh +3 -0
- package/context/scripts/telemetry/lib/transport.sh +87 -0
- package/context/scripts/telemetry/lib/usage.sh +205 -4
- package/context/scripts/telemetry/telemetry.conf +6 -0
- package/context/scripts/telemetry/telemetry.sh +48 -0
- package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
- package/docs/agent-usage-feedback-loop.md +35 -0
- package/docs/architecture-engine-and-kits.md +110 -0
- package/docs/context-map.md +2 -0
- package/docs/decisions/embeddable-engine.md +152 -0
- package/docs/decisions/index.md +3 -1
- package/docs/decisions/trust-ledger-retention.md +88 -0
- package/docs/decisions/workflow-enforcement.md +31 -9
- package/docs/fixture-ownership.md +3 -0
- package/docs/implementing-trust-reconciliation.md +129 -0
- package/docs/index.md +19 -9
- package/docs/integrations/flow-agents-console.md +167 -0
- package/docs/kit-authoring-guide.md +52 -21
- package/docs/spec/builder-flow-runtime.md +80 -0
- package/docs/spec/runtime-hook-surface.md +45 -1
- package/docs/specs/economics-record-contract.md +270 -0
- package/docs/specs/harness-capability-matrix.md +74 -0
- package/docs/specs/learning-review-proposals-contract.md +340 -0
- package/docs/specs/routing-efficiency-review.md +59 -0
- package/docs/verifiable-trust.md +74 -25
- package/docs/workflow-usage-guide.md +10 -0
- package/evals/acceptance/prove-capture-teeth.sh +132 -0
- package/evals/ci/antigaming-suite.sh +1 -0
- package/evals/ci/run-baseline.sh +72 -4
- package/evals/fixtures/economics/acceptance.json +12 -0
- package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
- package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
- package/evals/fixtures/economics/critique.json +22 -0
- package/evals/fixtures/economics/expected-record.json +71 -0
- package/evals/fixtures/economics/session-usage-event.json +1 -0
- package/evals/fixtures/economics/state.json +11 -0
- package/evals/fixtures/economics/transcript.jsonl +3 -0
- package/evals/fixtures/hook-influence/cases.json +7 -7
- package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
- package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
- package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
- package/evals/integration/test_builder_entry_enforcement.sh +241 -0
- package/evals/integration/test_builder_step_producers.sh +18 -10
- package/evals/integration/test_bundle_install.sh +172 -0
- package/evals/integration/test_console_tenant_isolation.sh +167 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
- package/evals/integration/test_dual_emit_flow_step.sh +10 -4
- package/evals/integration/test_economics_record.sh +674 -0
- package/evals/integration/test_effective_backlog_settings.sh +1 -1
- package/evals/integration/test_evidence_capture_hook.sh +17 -2
- package/evals/integration/test_exemption_usage_review.sh +198 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +83 -0
- package/evals/integration/test_flowdef_session_activation.sh +0 -1
- package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
- package/evals/integration/test_gate_lockdown.sh +7 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
- package/evals/integration/test_goal_fit_hook.sh +2031 -0
- package/evals/integration/test_hook_category_behaviors.sh +8 -1
- package/evals/integration/test_hook_influence_cases.sh +25 -1
- package/evals/integration/test_install_merge.sh +227 -2
- package/evals/integration/test_kit_conformance_levels.sh +6 -6
- package/evals/integration/test_learning_review_proposals.sh +329 -0
- package/evals/integration/test_liveness_conflict_injection.sh +26 -22
- package/evals/integration/test_liveness_console_relay.sh +166 -0
- package/evals/integration/test_liveness_heartbeat.sh +17 -17
- package/evals/integration/test_liveness_worktree_root.sh +575 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
- package/evals/integration/test_publish_delivery.sh +331 -1
- package/evals/integration/test_pull_work_board.sh +200 -0
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_record_check.sh +378 -0
- package/evals/integration/test_routing_efficiency.sh +71 -0
- package/evals/integration/test_runtime_adapter_activation.sh +28 -0
- package/evals/integration/test_session_resume_roundtrip.sh +16 -19
- package/evals/integration/test_skill_drift_check.sh +870 -0
- package/evals/integration/test_telemetry.sh +445 -0
- package/evals/integration/test_telemetry_doctor.sh +66 -0
- package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +30 -13
- package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
- package/evals/integration/test_usage_cost.sh +61 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
- package/evals/integration/test_workflow_steering_hook.sh +157 -16
- package/evals/integration/test_workspace_settings.sh +176 -0
- package/evals/lib/env.sh +26 -0
- package/evals/lib/node.sh +8 -0
- package/evals/run.sh +29 -0
- package/evals/static/test_ci_integration_coverage.sh +115 -0
- package/evals/static/test_declared_scope_forms_documented.sh +114 -0
- package/evals/static/test_universal_bundles.sh +34 -0
- package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
- package/evals/static/test_workflow_skills.sh +1 -1
- package/kits/builder/flows/build.flow.json +9 -18
- package/kits/builder/flows/publish-learn.flow.json +5 -1
- package/kits/builder/kit.json +120 -0
- package/kits/builder/skills/deliver/SKILL.md +42 -0
- package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -0
- package/kits/builder/skills/learning-review/SKILL.md +51 -0
- package/kits/builder/skills/plan-work/SKILL.md +17 -20
- package/kits/builder/skills/pull-work/SKILL.md +21 -0
- package/kits/builder/skills/release-readiness/SKILL.md +12 -0
- package/kits/knowledge/kit.json +9 -0
- package/kits/veritas-governance/docs/README.md +35 -7
- package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
- package/kits/veritas-governance/kit.json +14 -0
- package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
- package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
- package/package.json +2 -2
- package/packaging/manifest.json +29 -0
- package/schemas/backlog-provider-settings.schema.json +13 -0
- package/schemas/workflow-state.schema.json +44 -0
- package/scripts/README.md +4 -0
- package/scripts/check-content-boundary.cjs +8 -1
- package/scripts/ci/trust-reconcile.js +136 -0
- package/scripts/hooks/codex-hook-adapter.js +77 -2
- package/scripts/hooks/evidence-capture.js +38 -5
- package/scripts/hooks/lib/codex-exit-code.js +316 -0
- package/scripts/hooks/lib/kit-catalog.js +235 -0
- package/scripts/hooks/lib/liveness-write.js +28 -1
- package/scripts/hooks/lib/local-artifact-paths.js +97 -1
- package/scripts/hooks/lib/runnable-command.js +177 -0
- package/scripts/hooks/lib/skill-drift.js +350 -0
- package/scripts/hooks/stop-goal-fit.js +278 -48
- package/scripts/hooks/workflow-steering.js +121 -21
- package/scripts/install-codex-home.sh +97 -47
- package/scripts/install-merge.js +72 -14
- package/scripts/install-owned-files.js +178 -0
- package/scripts/liveness/relay.sh +84 -0
- package/scripts/telemetry/economics-record.schema.json +145 -0
- package/scripts/telemetry/economics-record.sh +331 -0
- package/scripts/telemetry/install-console-config.sh +25 -4
- package/scripts/telemetry/learning-review-decide.sh +124 -0
- package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
- package/scripts/telemetry/learning-review-proposals.sh +484 -0
- package/scripts/telemetry/lib/config.sh +102 -12
- package/scripts/telemetry/lib/pricing.sh +14 -6
- package/scripts/telemetry/lib/session.sh +3 -0
- package/scripts/telemetry/lib/transport.sh +133 -15
- package/scripts/telemetry/lib/usage.sh +121 -28
- package/scripts/telemetry/routing-efficiency.sh +0 -0
- package/scripts/telemetry/telemetry.conf +6 -0
- package/scripts/telemetry/telemetry.sh +48 -0
- package/src/builder-flow-run-adapter.ts +357 -0
- package/src/builder-flow-runtime.ts +348 -0
- package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
- package/src/cli/builder-flow-runtime.test.mjs +213 -0
- package/src/cli/builder-run.ts +28 -0
- package/src/cli/codex-agent-routing.test.mjs +44 -0
- package/src/cli/codex-exit-code.test.mjs +207 -0
- package/src/cli/console-connect-options.test.mjs +329 -0
- package/src/cli/console-telemetry-validate.test.mjs +157 -0
- package/src/cli/effective-backlog-settings.ts +68 -2
- package/src/cli/flow-resolver-composition.test.mjs +101 -0
- package/src/cli/init.test.mjs +161 -0
- package/src/cli/init.ts +407 -62
- package/src/cli/kit-metadata-security.test.mjs +443 -0
- package/src/cli/kit.ts +50 -12
- package/src/cli/pull-work-provider.ts +377 -3
- package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
- package/src/cli/skill-drift-check.ts +196 -0
- package/src/cli/telemetry-doctor.test.mjs +53 -0
- package/src/cli/telemetry-doctor.ts +50 -7
- package/src/cli/validate-hook-influence.ts +37 -6
- package/src/cli/workflow-sidecar.ts +1150 -151
- package/src/cli.ts +5 -0
- package/src/flow-kit/validate.ts +277 -38
- package/src/index.ts +19 -0
- package/src/lib/console-connect-options.ts +261 -0
- package/src/lib/console-telemetry-validate.ts +88 -0
- package/src/lib/flow-resolver.ts +153 -10
- package/src/lib/fs.ts +160 -0
- package/src/lib/local-artifact-root.ts +129 -3
- package/src/runtime-adapters.ts +113 -33
- package/src/tools/build-universal-bundles.ts +36 -2
- package/src/tools/codex-agent-routing.ts +48 -0
- package/src/tools/generate-context-map.ts +1 -0
- package/src/tools/validate-source-tree.ts +26 -1
package/build/src/cli/kit.js
CHANGED
|
@@ -5,10 +5,11 @@ import * as os from "node:os";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { parseArgs, flagBool, flagString } from "../lib/args.js";
|
|
8
|
-
import { assertPathContained,
|
|
8
|
+
import { assertPathContained, assertPathsDisjoint, atomicWriteJson, copyDirAtomic, ensureSafeDirectory, isoNow, readJson, walkFiles } from "../lib/fs.js";
|
|
9
9
|
import { assertKitRepository, deriveKitTargets, parseKitDependencies } from "../flow-kit/validate.js";
|
|
10
10
|
import { activateCodexLocal, activateStrandsLocal } from "../runtime-adapters.js";
|
|
11
11
|
import { defaultCodexHome } from "../lib/local-artifact-root.js";
|
|
12
|
+
import { root } from "../tools/common.js";
|
|
12
13
|
const REGISTRY_REL = path.join("kits", "local", "installed-kits.json");
|
|
13
14
|
const REPOSITORIES_REL = path.join("kits", "local", "repositories");
|
|
14
15
|
function registryPath(dest) { return path.join(dest, REGISTRY_REL); }
|
|
@@ -17,6 +18,23 @@ function resolveDest(flags) {
|
|
|
17
18
|
const explicit = flagString(flags, "dest");
|
|
18
19
|
return path.resolve(explicit ?? defaultCodexHome());
|
|
19
20
|
}
|
|
21
|
+
function resolveCatalogKitSource(source) {
|
|
22
|
+
if (!/^[a-z][a-z0-9-]*$/.test(source))
|
|
23
|
+
return null;
|
|
24
|
+
const repoCatalogPath = path.join(root, "kits", "catalog.json");
|
|
25
|
+
if (!fs.existsSync(repoCatalogPath))
|
|
26
|
+
return null;
|
|
27
|
+
const catalog = readJson(repoCatalogPath);
|
|
28
|
+
const entry = Array.isArray(catalog.kits)
|
|
29
|
+
? catalog.kits.find((item) => typeof item === "object" && item !== null && item.id === source)
|
|
30
|
+
: undefined;
|
|
31
|
+
if (!entry || typeof entry !== "object")
|
|
32
|
+
return null;
|
|
33
|
+
const rel = entry.path;
|
|
34
|
+
if (typeof rel !== "string")
|
|
35
|
+
return null;
|
|
36
|
+
return path.resolve(path.dirname(repoCatalogPath), "..", rel);
|
|
37
|
+
}
|
|
20
38
|
function loadRegistry(dest) {
|
|
21
39
|
const file = registryPath(dest);
|
|
22
40
|
if (!fs.existsSync(file))
|
|
@@ -96,7 +114,7 @@ async function install(argv) {
|
|
|
96
114
|
if (isGitUrl) {
|
|
97
115
|
return await installGitSource(source, argv);
|
|
98
116
|
}
|
|
99
|
-
return await installLocalSource(path.resolve(source), argv);
|
|
117
|
+
return await installLocalSource(resolveCatalogKitSource(source) ?? path.resolve(source), argv);
|
|
100
118
|
}
|
|
101
119
|
async function installLocalSource(source, argv) {
|
|
102
120
|
const args = parseArgs(argv);
|
|
@@ -112,13 +130,26 @@ async function installLocalSource(source, argv) {
|
|
|
112
130
|
return 1;
|
|
113
131
|
}
|
|
114
132
|
const kitId = String(manifest.id);
|
|
133
|
+
const target = installedPath(dest, kitId);
|
|
134
|
+
try {
|
|
135
|
+
assertPathsDisjoint(source, target);
|
|
136
|
+
ensureSafeDirectory(dest, dest);
|
|
137
|
+
assertPathContained(dest, target);
|
|
138
|
+
ensureSafeDirectory(dest, path.dirname(target));
|
|
139
|
+
const registryFile = registryPath(dest);
|
|
140
|
+
ensureSafeDirectory(dest, path.dirname(registryFile));
|
|
141
|
+
if (fs.existsSync(registryFile) && fs.lstatSync(registryFile).isSymbolicLink()) {
|
|
142
|
+
throw new Error(`refusing to read or replace symlink: ${registryFile}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error(`install: unsafe source or destination: ${error.message}`);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
115
149
|
warnUninstalledDependencies(manifest, path.join(source, "kit.json"), dest);
|
|
116
150
|
const hash = contentHash(source);
|
|
117
151
|
const registry = loadRegistry(dest);
|
|
118
152
|
const existing = registry.kits.find((entry) => entry.id === kitId);
|
|
119
|
-
const target = installedPath(dest, kitId);
|
|
120
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
121
|
-
assertPathContained(dest, target);
|
|
122
153
|
const sourceText = source;
|
|
123
154
|
if (existing && existing.source !== sourceText && !flagBool(args.flags, "update")) {
|
|
124
155
|
console.log(`conflict: kit '${kitId}' is already installed from ${existing.source}; rerun with --update to replace it`);
|
|
@@ -128,7 +159,7 @@ async function installLocalSource(source, argv) {
|
|
|
128
159
|
console.log(`kit '${kitId}' is already installed from ${sourceText}`);
|
|
129
160
|
return 0;
|
|
130
161
|
}
|
|
131
|
-
|
|
162
|
+
copyDirAtomic(dest, source, target);
|
|
132
163
|
const entry = {
|
|
133
164
|
id: kitId,
|
|
134
165
|
source: sourceText,
|
|
@@ -140,7 +171,7 @@ async function installLocalSource(source, argv) {
|
|
|
140
171
|
if (typeof manifest.version === "string" && manifest.version)
|
|
141
172
|
entry.version = manifest.version;
|
|
142
173
|
registry.kits = existing ? registry.kits.map((item) => item.id === kitId ? entry : item) : [...registry.kits, entry];
|
|
143
|
-
|
|
174
|
+
atomicWriteJson(dest, registryPath(dest), registry);
|
|
144
175
|
console.log(`${existing ? "updated" : "installed"} local kit '${kitId}' at ${target}`);
|
|
145
176
|
return 0;
|
|
146
177
|
}
|
|
@@ -195,8 +226,20 @@ async function installGitSource(rawUrl, argv) {
|
|
|
195
226
|
const registry = loadRegistry(dest);
|
|
196
227
|
const existing = registry.kits.find((entry) => entry.id === kitId);
|
|
197
228
|
const target = installedPath(dest, kitId);
|
|
198
|
-
|
|
199
|
-
|
|
229
|
+
try {
|
|
230
|
+
ensureSafeDirectory(dest, dest);
|
|
231
|
+
assertPathContained(dest, target);
|
|
232
|
+
ensureSafeDirectory(dest, path.dirname(target));
|
|
233
|
+
const registryFile = registryPath(dest);
|
|
234
|
+
ensureSafeDirectory(dest, path.dirname(registryFile));
|
|
235
|
+
if (fs.existsSync(registryFile) && fs.lstatSync(registryFile).isSymbolicLink()) {
|
|
236
|
+
throw new Error(`refusing to read or replace symlink: ${registryFile}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error(`install: unsafe destination: ${error.message}`);
|
|
241
|
+
return 1;
|
|
242
|
+
}
|
|
200
243
|
const sourceText = repoUrl + (ref ? `#${ref}` : "");
|
|
201
244
|
if (existing && existing.source !== sourceText && !update) {
|
|
202
245
|
console.log(`conflict: kit '${kitId}' is already installed from ${existing.source}; rerun with --update to replace it`);
|
|
@@ -206,7 +249,7 @@ async function installGitSource(rawUrl, argv) {
|
|
|
206
249
|
console.log(`kit '${kitId}' is already installed from ${sourceText}`);
|
|
207
250
|
return 0;
|
|
208
251
|
}
|
|
209
|
-
|
|
252
|
+
copyDirAtomic(dest, tmpBase, target);
|
|
210
253
|
const entry = {
|
|
211
254
|
id: kitId,
|
|
212
255
|
source: sourceText,
|
|
@@ -218,7 +261,7 @@ async function installGitSource(rawUrl, argv) {
|
|
|
218
261
|
if (typeof manifest.version === "string" && manifest.version)
|
|
219
262
|
entry.version = manifest.version;
|
|
220
263
|
registry.kits = existing ? registry.kits.map((item) => item.id === kitId ? entry : item) : [...registry.kits, entry];
|
|
221
|
-
|
|
264
|
+
atomicWriteJson(dest, registryPath(dest), registry);
|
|
222
265
|
console.log(`${existing ? "updated" : "installed"} git kit '${kitId}' from ${sourceText} at ${target}`);
|
|
223
266
|
return 0;
|
|
224
267
|
}
|
|
@@ -313,7 +356,7 @@ async function inspect(argv) {
|
|
|
313
356
|
return 1;
|
|
314
357
|
}
|
|
315
358
|
// Pass the real kitDir so @kontourai/flow can validate flow file existence for K0.
|
|
316
|
-
const result = await deriveKitTargets(manifest, kitDir);
|
|
359
|
+
const result = await deriveKitTargets(manifest, kitDir, root);
|
|
317
360
|
console.log(JSON.stringify(result, null, 2));
|
|
318
361
|
return result.conformance.k0 ? 0 : 1;
|
|
319
362
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { parseArgs, flagList, flagString } from "../lib/args.js";
|
|
4
5
|
const FLOW_ARTIFACT_PATTERN = /(?<path>\.kontourai\/flow-agents\/[^\s`'")]+)/g;
|
|
@@ -11,6 +12,12 @@ const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
11
12
|
function loadJson(file) {
|
|
12
13
|
return file === "-" ? JSON.parse(fs.readFileSync(0, "utf8")) : JSON.parse(fs.readFileSync(file, "utf8"));
|
|
13
14
|
}
|
|
15
|
+
function asRecord(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
17
|
+
}
|
|
18
|
+
function arrayValue(value) {
|
|
19
|
+
return Array.isArray(value) ? value : [];
|
|
20
|
+
}
|
|
14
21
|
function names(values) {
|
|
15
22
|
return Array.isArray(values) ? values.flatMap((item) => typeof item === "object" && item !== null && typeof item.name === "string" ? [item.name] : []) : [];
|
|
16
23
|
}
|
|
@@ -18,7 +25,33 @@ function statusKey(value) {
|
|
|
18
25
|
if (typeof value !== "string")
|
|
19
26
|
return null;
|
|
20
27
|
const key = value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
21
|
-
return { todo: "todo", to_do: "todo", ready: "ready", doing: "in_progress", in_progress: "in_progress", blocked: "blocked", review: "review", verification: "verification", done: "done", closed: "done" }[key] ?? key;
|
|
28
|
+
return { open: "todo", todo: "todo", to_do: "todo", ready: "ready", doing: "in_progress", in_progress: "in_progress", blocked: "blocked", review: "review", verification: "verification", done: "done", closed: "done" }[key] ?? key;
|
|
29
|
+
}
|
|
30
|
+
function normalizedText(value) {
|
|
31
|
+
return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : null;
|
|
32
|
+
}
|
|
33
|
+
function repoKey(owner, repo, number) {
|
|
34
|
+
const issueNumber = Number(number);
|
|
35
|
+
return typeof owner === "string" && typeof repo === "string" && Number.isFinite(issueNumber)
|
|
36
|
+
? `${owner}/${repo}#${issueNumber}`
|
|
37
|
+
: null;
|
|
38
|
+
}
|
|
39
|
+
function configuredRepos(settings) {
|
|
40
|
+
const workProvider = settings.work_item_provider;
|
|
41
|
+
const boardProvider = (settings.board_provider ?? {});
|
|
42
|
+
const workRepo = workProvider.repo;
|
|
43
|
+
const board = (boardProvider.board ?? {});
|
|
44
|
+
const defaultOwner = stringValue(board.owner) ?? stringValue(boardProvider.repo?.owner) ?? workRepo.owner;
|
|
45
|
+
const workspace = asRecord(settings.workspace);
|
|
46
|
+
const repos = arrayValue(workspace?.repos).flatMap((repo) => {
|
|
47
|
+
if (typeof repo === "string" && repo.trim())
|
|
48
|
+
return [{ owner: defaultOwner, name: repo.trim() }];
|
|
49
|
+
const record = asRecord(repo);
|
|
50
|
+
const owner = stringValue(record?.owner) ?? defaultOwner;
|
|
51
|
+
const name = stringValue(record?.name);
|
|
52
|
+
return owner && name ? [{ owner, name }] : [];
|
|
53
|
+
});
|
|
54
|
+
return repos.length ? repos : [{ owner: workRepo.owner, name: workRepo.name }];
|
|
22
55
|
}
|
|
23
56
|
function projectStatus(issue) {
|
|
24
57
|
for (const item of (Array.isArray(issue.projectItems) ? issue.projectItems : [])) {
|
|
@@ -393,6 +426,304 @@ function normalize(issue, settings) {
|
|
|
393
426
|
created_at: issue.createdAt,
|
|
394
427
|
};
|
|
395
428
|
}
|
|
429
|
+
function fieldValueByName(item, fieldName) {
|
|
430
|
+
const wanted = fieldName.toLowerCase();
|
|
431
|
+
const direct = item[fieldName] ?? item[fieldName.toLowerCase()];
|
|
432
|
+
if (typeof direct === "string")
|
|
433
|
+
return direct;
|
|
434
|
+
const fieldValues = Array.isArray(item.fieldValues) ? item.fieldValues : arrayValue(asRecord(item.fieldValues)?.nodes);
|
|
435
|
+
for (const value of fieldValues) {
|
|
436
|
+
const record = asRecord(value);
|
|
437
|
+
if (!record)
|
|
438
|
+
continue;
|
|
439
|
+
const field = asRecord(record.field);
|
|
440
|
+
const name = normalizedText(field?.name);
|
|
441
|
+
if (name !== wanted)
|
|
442
|
+
continue;
|
|
443
|
+
return stringValue(record.name) ?? stringValue(record.text) ?? stringValue(record.value) ?? stringValue(record.optionName);
|
|
444
|
+
}
|
|
445
|
+
return undefined;
|
|
446
|
+
}
|
|
447
|
+
function normalizeBoardIssue(rawIssue, fallbackOwner, fallbackRepo) {
|
|
448
|
+
const issue = asRecord(rawIssue);
|
|
449
|
+
if (!issue)
|
|
450
|
+
return null;
|
|
451
|
+
const number = Number(issue.number);
|
|
452
|
+
if (!Number.isFinite(number))
|
|
453
|
+
return null;
|
|
454
|
+
const repository = asRecord(issue.repository);
|
|
455
|
+
const owner = stringValue(issue.owner) ?? stringValue(asRecord(repository?.owner)?.login) ?? fallbackOwner;
|
|
456
|
+
const repo = stringValue(issue.repo) ?? stringValue(issue.repositoryName) ?? stringValue(repository?.name) ?? fallbackRepo;
|
|
457
|
+
return {
|
|
458
|
+
id: stringValue(issue.id),
|
|
459
|
+
node_id: stringValue(issue.node_id) ?? stringValue(issue.id),
|
|
460
|
+
owner,
|
|
461
|
+
repo,
|
|
462
|
+
number,
|
|
463
|
+
title: stringValue(issue.title) ?? `#${number}`,
|
|
464
|
+
body: stringValue(issue.body) ?? "",
|
|
465
|
+
state: stringValue(issue.state) ?? "OPEN",
|
|
466
|
+
labels: names(issue.labels).concat(arrayValue(asRecord(issue.labels)?.nodes).flatMap((label) => stringValue(asRecord(label)?.name) ?? [])),
|
|
467
|
+
url: stringValue(issue.url),
|
|
468
|
+
updatedAt: stringValue(issue.updatedAt),
|
|
469
|
+
createdAt: stringValue(issue.createdAt),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function normalizeBoardItem(rawItem, index, fallbackOwner, fallbackRepo) {
|
|
473
|
+
const item = asRecord(rawItem);
|
|
474
|
+
if (!item)
|
|
475
|
+
return null;
|
|
476
|
+
const content = item.content ?? item.issue;
|
|
477
|
+
const issue = normalizeBoardIssue(content, fallbackOwner, fallbackRepo);
|
|
478
|
+
if (!issue)
|
|
479
|
+
return null;
|
|
480
|
+
const rawPosition = item.position ?? item.databaseId ?? item.index;
|
|
481
|
+
const position = Number.isFinite(Number(rawPosition)) ? Number(rawPosition) : index;
|
|
482
|
+
return {
|
|
483
|
+
id: stringValue(item.id),
|
|
484
|
+
position,
|
|
485
|
+
status: fieldValueByName(item, "Status") ?? stringValue(item.status),
|
|
486
|
+
priority: fieldValueByName(item, "Priority") ?? stringValue(item.priority),
|
|
487
|
+
issue,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function boardItemsFromDoc(doc, settings) {
|
|
491
|
+
const workProvider = settings.work_item_provider;
|
|
492
|
+
const repo = workProvider.repo;
|
|
493
|
+
const record = asRecord(doc);
|
|
494
|
+
const rawItems = Array.isArray(doc)
|
|
495
|
+
? doc
|
|
496
|
+
: arrayValue(record?.project_items).length ? arrayValue(record?.project_items)
|
|
497
|
+
: arrayValue(record?.projectItems).length ? arrayValue(record?.projectItems)
|
|
498
|
+
: arrayValue(record?.items).length ? arrayValue(record?.items)
|
|
499
|
+
: arrayValue(asRecord(asRecord(asRecord(record?.data)?.organization)?.projectV2)?.items).length
|
|
500
|
+
? arrayValue(asRecord(asRecord(asRecord(record?.data)?.organization)?.projectV2)?.items)
|
|
501
|
+
: [];
|
|
502
|
+
const nodes = rawItems.flatMap((item) => asRecord(item)?.nodes && Array.isArray(asRecord(item)?.nodes) ? arrayValue(asRecord(item)?.nodes) : [item]);
|
|
503
|
+
return nodes.flatMap((item, index) => normalizeBoardItem(item, index, repo.owner, repo.name) ?? []);
|
|
504
|
+
}
|
|
505
|
+
function openIssuesFromDoc(doc, settings) {
|
|
506
|
+
const repos = configuredRepos(settings);
|
|
507
|
+
const fallback = repos.length === 1 ? repos[0] : undefined;
|
|
508
|
+
const record = asRecord(doc);
|
|
509
|
+
const rawOpenIssues = record?.open_issues;
|
|
510
|
+
const rawIssues = arrayValue(rawOpenIssues).length ? arrayValue(rawOpenIssues)
|
|
511
|
+
: rawOpenIssues && typeof rawOpenIssues === "object" && !Array.isArray(rawOpenIssues)
|
|
512
|
+
? Object.entries(rawOpenIssues).flatMap(([repoName, issues]) => arrayValue(issues).map((issue) => ({ issue, repoName })))
|
|
513
|
+
: arrayValue(record?.issues).length ? arrayValue(record?.issues)
|
|
514
|
+
: arrayValue(asRecord(asRecord(asRecord(record?.data)?.repository)?.issues)?.nodes);
|
|
515
|
+
return rawIssues.flatMap((issue) => {
|
|
516
|
+
const wrapped = asRecord(issue);
|
|
517
|
+
const rawIssue = wrapped && "issue" in wrapped ? wrapped.issue : issue;
|
|
518
|
+
const repoName = wrapped && typeof wrapped.repoName === "string" ? wrapped.repoName : undefined;
|
|
519
|
+
const repoFallback = repoName ? repos.find((repo) => repo.name === repoName) : fallback;
|
|
520
|
+
const normalized = normalizeBoardIssue(rawIssue, repoFallback?.owner, repoName ?? repoFallback?.name);
|
|
521
|
+
return normalized && (normalized.state ?? "OPEN").toUpperCase() === "OPEN" ? [normalized] : [];
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
function boardCandidate(item, settings) {
|
|
525
|
+
const workProvider = settings.work_item_provider;
|
|
526
|
+
const boardProvider = (settings.board_provider ?? {});
|
|
527
|
+
const issue = item.issue;
|
|
528
|
+
const owner = issue.owner ?? (workProvider.repo.owner);
|
|
529
|
+
const repo = issue.repo ?? (workProvider.repo.name);
|
|
530
|
+
const body = issue.body ?? "";
|
|
531
|
+
const metadata = parseWorkItemMetadata(body);
|
|
532
|
+
const rawStatus = item.status ?? "Todo";
|
|
533
|
+
return {
|
|
534
|
+
id: `github:${owner}/${repo}#${issue.number}`,
|
|
535
|
+
title: issue.title,
|
|
536
|
+
body,
|
|
537
|
+
status: statusKey(rawStatus) ?? rawStatus.toLowerCase(),
|
|
538
|
+
labels: issue.labels,
|
|
539
|
+
priority: item.priority,
|
|
540
|
+
...sourceRevisionFields(metadata, owner, repo),
|
|
541
|
+
blockers: mergeBlockers(normalizeStructuredBlockers(metadata, owner, repo), blockers(body, owner, repo)),
|
|
542
|
+
related_links: extractRefs(body, owner, repo),
|
|
543
|
+
source_provider: { role: "WorkItemProvider", kind: workProvider.kind, owner, repo, number: issue.number, url: issue.url, state: issue.state, node_id: issue.node_id ?? issue.id, capabilities: workProvider.capabilities ?? [] },
|
|
544
|
+
board_membership: {
|
|
545
|
+
role: "BoardProvider",
|
|
546
|
+
kind: boardProvider.kind,
|
|
547
|
+
board: boardProvider.board,
|
|
548
|
+
project_item_id: item.id,
|
|
549
|
+
position: item.position,
|
|
550
|
+
status: rawStatus,
|
|
551
|
+
priority: item.priority,
|
|
552
|
+
capabilities: boardProvider.capabilities ?? [],
|
|
553
|
+
},
|
|
554
|
+
artifact_refs: Array.from(body.matchAll(FLOW_ARTIFACT_PATTERN), (m) => (m.groups?.path ?? "").replace(/[.,]+$/, "")),
|
|
555
|
+
updated_at: issue.updatedAt,
|
|
556
|
+
created_at: issue.createdAt,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function priorityRank(priority) {
|
|
560
|
+
const key = String(priority ?? "").trim().toUpperCase();
|
|
561
|
+
return { P0: 0, P1: 1, P2: 2 }[key] ?? 99;
|
|
562
|
+
}
|
|
563
|
+
function boardResult(settings, boardItems, openIssues) {
|
|
564
|
+
const selection = (settings.selection ?? {});
|
|
565
|
+
const filters = (selection.filters ?? {});
|
|
566
|
+
const readyStatuses = new Set((filters.ready_statuses ?? ["ready"]).map((status) => status.toLowerCase()));
|
|
567
|
+
const workProvider = settings.work_item_provider;
|
|
568
|
+
const repo = workProvider.repo;
|
|
569
|
+
const candidates = boardItems.map((item) => ({ item, candidate: boardCandidate(item, settings) }));
|
|
570
|
+
const readyQueue = candidates
|
|
571
|
+
.filter(({ item }) => readyStatuses.has(String(statusKey(item.status) ?? item.status ?? "").toLowerCase()))
|
|
572
|
+
.sort((left, right) => priorityRank(left.item.priority) - priorityRank(right.item.priority) || left.item.position - right.item.position)
|
|
573
|
+
.map(({ candidate }) => candidate);
|
|
574
|
+
const boardIssueKeys = new Set(boardItems.flatMap((item) => {
|
|
575
|
+
const issue = item.issue;
|
|
576
|
+
const key = repoKey(issue?.owner ?? repo.owner, issue?.repo ?? repo.name, issue?.number);
|
|
577
|
+
return key ? [key] : [];
|
|
578
|
+
}));
|
|
579
|
+
const intakeGaps = openIssues
|
|
580
|
+
.filter((issue) => !boardIssueKeys.has(repoKey(issue.owner ?? repo.owner, issue.repo ?? repo.name, issue.number) ?? ""))
|
|
581
|
+
.map((issue) => ({
|
|
582
|
+
id: `github:${issue.owner ?? repo.owner}/${issue.repo ?? repo.name}#${issue.number}`,
|
|
583
|
+
title: issue.title,
|
|
584
|
+
status: statusKey(issue.state) ?? "todo",
|
|
585
|
+
labels: issue.labels,
|
|
586
|
+
source_provider: { role: "WorkItemProvider", kind: workProvider.kind, owner: issue.owner ?? repo.owner, repo: issue.repo ?? repo.name, number: issue.number, url: issue.url, state: issue.state, node_id: issue.node_id ?? issue.id, capabilities: workProvider.capabilities ?? [] },
|
|
587
|
+
}));
|
|
588
|
+
const warnings = readyQueue.length === 0
|
|
589
|
+
? [{ code: "zero_ready_items", message: "Configured BoardProvider yielded zero ready items; treat this as a dead readiness source and do not silently fall back to WorkItemProvider issue listing." }]
|
|
590
|
+
: [];
|
|
591
|
+
return { ready_queue: readyQueue, intake_gaps: intakeGaps, warnings };
|
|
592
|
+
}
|
|
593
|
+
const PROJECT_BOARD_QUERY = `
|
|
594
|
+
query($org: String!, $projectNumber: Int!, $repoOwner: String!, $repoName: String!, $itemCursor: String, $issueCursor: String) {
|
|
595
|
+
organization(login: $org) {
|
|
596
|
+
projectV2(number: $projectNumber) {
|
|
597
|
+
items(first: 100, after: $itemCursor) {
|
|
598
|
+
pageInfo { hasNextPage endCursor }
|
|
599
|
+
nodes {
|
|
600
|
+
id
|
|
601
|
+
fieldValues(first: 30) {
|
|
602
|
+
nodes {
|
|
603
|
+
... on ProjectV2ItemFieldSingleSelectValue { name field { ... on ProjectV2FieldCommon { name } } }
|
|
604
|
+
... on ProjectV2ItemFieldTextValue { text field { ... on ProjectV2FieldCommon { name } } }
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
content {
|
|
608
|
+
... on Issue {
|
|
609
|
+
id
|
|
610
|
+
number
|
|
611
|
+
title
|
|
612
|
+
body
|
|
613
|
+
state
|
|
614
|
+
url
|
|
615
|
+
updatedAt
|
|
616
|
+
createdAt
|
|
617
|
+
repository { name owner { login } }
|
|
618
|
+
labels(first: 50) { nodes { name } }
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
repository(owner: $repoOwner, name: $repoName) {
|
|
626
|
+
issues(first: 100, after: $issueCursor, states: OPEN) {
|
|
627
|
+
pageInfo { hasNextPage endCursor }
|
|
628
|
+
nodes {
|
|
629
|
+
id
|
|
630
|
+
number
|
|
631
|
+
title
|
|
632
|
+
body
|
|
633
|
+
state
|
|
634
|
+
url
|
|
635
|
+
updatedAt
|
|
636
|
+
createdAt
|
|
637
|
+
repository { name owner { login } }
|
|
638
|
+
labels(first: 50) { nodes { name } }
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}`;
|
|
643
|
+
const REPO_ISSUES_QUERY = `
|
|
644
|
+
query($repoOwner: String!, $repoName: String!, $issueCursor: String) {
|
|
645
|
+
repository(owner: $repoOwner, name: $repoName) {
|
|
646
|
+
issues(first: 100, after: $issueCursor, states: OPEN) {
|
|
647
|
+
pageInfo { hasNextPage endCursor }
|
|
648
|
+
nodes {
|
|
649
|
+
id
|
|
650
|
+
number
|
|
651
|
+
title
|
|
652
|
+
body
|
|
653
|
+
state
|
|
654
|
+
url
|
|
655
|
+
updatedAt
|
|
656
|
+
createdAt
|
|
657
|
+
repository { name owner { login } }
|
|
658
|
+
labels(first: 50) { nodes { name } }
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}`;
|
|
663
|
+
function ghGraphql(query, variables) {
|
|
664
|
+
const args = ["api", "graphql", "-f", `query=${query}`];
|
|
665
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
666
|
+
if (value !== undefined && value !== "")
|
|
667
|
+
args.push("-F", `${key}=${value}`);
|
|
668
|
+
}
|
|
669
|
+
return JSON.parse(execFileSync("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }));
|
|
670
|
+
}
|
|
671
|
+
function liveBoardDoc(settings) {
|
|
672
|
+
const workProvider = settings.work_item_provider;
|
|
673
|
+
const boardProvider = settings.board_provider;
|
|
674
|
+
const workRepo = workProvider.repo;
|
|
675
|
+
const repos = configuredRepos(settings);
|
|
676
|
+
const board = boardProvider.board;
|
|
677
|
+
const org = stringValue(board.owner) ?? stringValue(boardProvider.repo?.owner) ?? workRepo.owner;
|
|
678
|
+
const projectNumber = Number(board.number);
|
|
679
|
+
if (!org || !Number.isFinite(projectNumber))
|
|
680
|
+
throw new Error("BoardProvider must configure board.owner or repo.owner and board.number for live reads");
|
|
681
|
+
const items = [];
|
|
682
|
+
const issues = [];
|
|
683
|
+
let itemCursor = "";
|
|
684
|
+
let issueCursor = "";
|
|
685
|
+
let fetchItems = true;
|
|
686
|
+
let fetchIssues = true;
|
|
687
|
+
while (fetchItems || fetchIssues) {
|
|
688
|
+
const page = ghGraphql(PROJECT_BOARD_QUERY, {
|
|
689
|
+
org,
|
|
690
|
+
projectNumber,
|
|
691
|
+
repoOwner: repos[0]?.owner ?? workRepo.owner,
|
|
692
|
+
repoName: repos[0]?.name ?? workRepo.name,
|
|
693
|
+
itemCursor,
|
|
694
|
+
issueCursor,
|
|
695
|
+
});
|
|
696
|
+
const projectItems = asRecord(asRecord(asRecord(page.data)?.organization)?.projectV2)?.items;
|
|
697
|
+
const repoIssues = asRecord(asRecord(page.data)?.repository)?.issues;
|
|
698
|
+
if (fetchItems)
|
|
699
|
+
items.push(...arrayValue(projectItems?.nodes));
|
|
700
|
+
if (fetchIssues)
|
|
701
|
+
issues.push(...arrayValue(repoIssues?.nodes));
|
|
702
|
+
const itemPageInfo = asRecord(projectItems?.pageInfo);
|
|
703
|
+
const issuePageInfo = asRecord(repoIssues?.pageInfo);
|
|
704
|
+
fetchItems = Boolean(itemPageInfo?.hasNextPage);
|
|
705
|
+
fetchIssues = Boolean(issuePageInfo?.hasNextPage);
|
|
706
|
+
itemCursor = stringValue(itemPageInfo?.endCursor) ?? "";
|
|
707
|
+
issueCursor = stringValue(issuePageInfo?.endCursor) ?? "";
|
|
708
|
+
}
|
|
709
|
+
for (const repo of repos.slice(1)) {
|
|
710
|
+
let repoIssueCursor = "";
|
|
711
|
+
let fetchRepoIssues = true;
|
|
712
|
+
while (fetchRepoIssues) {
|
|
713
|
+
const page = ghGraphql(REPO_ISSUES_QUERY, {
|
|
714
|
+
repoOwner: repo.owner,
|
|
715
|
+
repoName: repo.name,
|
|
716
|
+
issueCursor: repoIssueCursor,
|
|
717
|
+
});
|
|
718
|
+
const repoIssues = asRecord(asRecord(page.data)?.repository)?.issues;
|
|
719
|
+
issues.push(...arrayValue(repoIssues?.nodes));
|
|
720
|
+
const issuePageInfo = asRecord(repoIssues?.pageInfo);
|
|
721
|
+
fetchRepoIssues = Boolean(issuePageInfo?.hasNextPage);
|
|
722
|
+
repoIssueCursor = stringValue(issuePageInfo?.endCursor) ?? "";
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return { items, issues };
|
|
726
|
+
}
|
|
396
727
|
function classify(item, settings, resolved) {
|
|
397
728
|
const selection = (settings.selection ?? {});
|
|
398
729
|
const filters = (selection.filters ?? {});
|
|
@@ -434,16 +765,21 @@ function resolveSettings(doc) {
|
|
|
434
765
|
return doc;
|
|
435
766
|
if (Array.isArray(doc.projects) && doc.projects.length)
|
|
436
767
|
return doc.projects[0];
|
|
437
|
-
if (typeof doc.defaults === "object" && doc.defaults !== null)
|
|
438
|
-
|
|
768
|
+
if (typeof doc.defaults === "object" && doc.defaults !== null) {
|
|
769
|
+
const settings = structuredClone(doc.defaults);
|
|
770
|
+
if (typeof doc.workspace === "object" && doc.workspace !== null)
|
|
771
|
+
settings.workspace = doc.workspace;
|
|
772
|
+
return settings;
|
|
773
|
+
}
|
|
439
774
|
throw new Error("settings JSON must be an effective settings object or backlog-provider-settings document");
|
|
440
775
|
}
|
|
441
776
|
export function main(argv = process.argv.slice(2)) {
|
|
442
777
|
const args = parseArgs(argv);
|
|
443
778
|
const settingsJson = flagString(args.flags, "settings-json");
|
|
444
779
|
const issuesJson = flagString(args.flags, "issues-json");
|
|
445
|
-
|
|
446
|
-
|
|
780
|
+
const itemsJson = flagString(args.flags, "items-json");
|
|
781
|
+
if (!settingsJson)
|
|
782
|
+
throw new Error("--settings-json is required");
|
|
447
783
|
const now = new Date(flagString(args.flags, "now") ?? Date.now());
|
|
448
784
|
if (Number.isNaN(now.getTime()))
|
|
449
785
|
throw new Error("--now must be a valid date/time when provided");
|
|
@@ -455,6 +791,11 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
455
791
|
now,
|
|
456
792
|
};
|
|
457
793
|
const settings = resolveSettings(loadJson(settingsJson));
|
|
794
|
+
if (itemsJson || !issuesJson) {
|
|
795
|
+
const doc = itemsJson ? loadJson(itemsJson) : liveBoardDoc(settings);
|
|
796
|
+
console.log(JSON.stringify(boardResult(settings, boardItemsFromDoc(doc, settings), openIssuesFromDoc(doc, settings)), null, 2));
|
|
797
|
+
return 0;
|
|
798
|
+
}
|
|
458
799
|
const issuesDoc = loadJson(issuesJson);
|
|
459
800
|
const issues = Array.isArray(issuesDoc) ? issuesDoc : (issuesDoc.items ?? [issuesDoc]);
|
|
460
801
|
const resolved = Object.fromEntries(flagList(args.flags, "resolved-ref").map((item) => item.split("=", 2)));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(argv?: string[]): Promise<number>;
|