@kontourai/flow-agents 2.1.1 → 2.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/.github/CODEOWNERS +2 -0
- package/CHANGELOG.md +29 -0
- package/build/src/cli/sidecar-claim-explain.d.ts +45 -0
- package/build/src/cli/sidecar-claim-explain.js +87 -0
- package/build/src/cli/telemetry-doctor.js +2 -4
- package/build/src/cli/usage-feedback.js +16 -8
- package/build/src/cli/workflow-artifact-cleanup-audit.js +4 -2
- package/build/src/cli/workflow-sidecar.d.ts +2 -44
- package/build/src/cli/workflow-sidecar.js +18 -87
- package/build/src/index.d.ts +1 -0
- package/build/src/index.js +1 -0
- package/build/src/lib/local-artifact-root.d.ts +11 -0
- package/build/src/lib/local-artifact-root.js +30 -0
- package/build/src/tools/validate-source-tree.js +1 -0
- package/context/scripts/telemetry/lib/config.sh +3 -4
- package/docs/adr/0018-freeze-local-shell-heuristics.md +95 -0
- package/docs/repository-structure.md +7 -3
- package/evals/integration/test_bundle_lifecycle.sh +9 -9
- package/evals/integration/test_gate_lockdown.sh +4 -0
- package/evals/integration/test_migrate_local_artifacts.sh +102 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +34 -0
- package/evals/run.sh +2 -0
- package/evals/static/test_unit_helpers.sh +26 -0
- package/integrations/strands-ts/package.json +3 -0
- package/integrations/strands-ts/src/telemetry.ts +31 -50
- package/kits/knowledge/adapters/flow-runner/index.js +1 -1
- package/kits/knowledge/adapters/flow-runner/telemetry.js +2 -2
- package/package.json +2 -1
- package/scripts/README.md +1 -0
- package/scripts/hooks/config-protection.js +6 -0
- package/scripts/hooks/evidence-capture.js +16 -10
- package/scripts/hooks/lib/local-artifact-paths.js +32 -0
- package/scripts/hooks/stop-goal-fit.js +19 -13
- package/scripts/hooks/workflow-steering.js +5 -3
- package/scripts/lib/command-log-chain.js +5 -0
- package/scripts/migrate-local-artifacts.mjs +230 -0
- package/scripts/telemetry/lib/config.sh +3 -4
- package/src/cli/public-api.test.mjs +21 -0
- package/src/cli/sidecar-claim-explain.ts +130 -0
- package/src/cli/sidecar-pure-helpers.test.mjs +168 -0
- package/src/cli/telemetry-doctor.ts +2 -4
- package/src/cli/usage-feedback.ts +15 -8
- package/src/cli/workflow-artifact-cleanup-audit.ts +4 -2
- package/src/cli/workflow-sidecar.ts +17 -131
- package/src/index.ts +14 -0
- package/src/lib/local-artifact-root.ts +39 -0
- package/src/tools/validate-source-tree.ts +1 -0
package/.github/CODEOWNERS
CHANGED
|
@@ -27,3 +27,5 @@
|
|
|
27
27
|
# FlowDefinition resolution + the trust producer/checkpoint/signing the gate depends on
|
|
28
28
|
/src/lib/flow-resolver.ts @briananderson1222
|
|
29
29
|
/src/cli/workflow-sidecar.ts @briananderson1222
|
|
30
|
+
# Modules decomposed out of workflow-sidecar.ts (ops#22) keep the same ownership.
|
|
31
|
+
/src/cli/sidecar-*.ts @briananderson1222
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.3.0](https://github.com/kontourai/flow-agents/compare/v2.2.0...v2.3.0) (2026-07-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **strands-ts:** consume @kontourai/console-telemetry for pricing/cost ([eb878b0](https://github.com/kontourai/flow-agents/commit/eb878b024a166b68d5cef00cd659311de9c7b49b))
|
|
9
|
+
|
|
10
|
+
## [2.2.0](https://github.com/kontourai/flow-agents/compare/v2.1.1...v2.2.0) (2026-06-30)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **strands-ts:** consume @kontourai/console-telemetry for pricing/cost ([#243](https://github.com/kontourai/flow-agents/issues/243)) ([eba7ff3](https://github.com/kontourai/flow-agents/commit/eba7ff39433f602e7e17b17164a84fa187aa3dc8))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Fixes
|
|
19
|
+
|
|
20
|
+
* **flow-agents:** expose kontourai artifact roots ([#258](https://github.com/kontourai/flow-agents/issues/258)) ([82db131](https://github.com/kontourai/flow-agents/commit/82db1319e9115820c606c71c40087c05b1278fce))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
|
|
25
|
+
* **flow-agents:** ADR 0018 — freeze local shell-parsing heuristics (ops[#21](https://github.com/kontourai/flow-agents/issues/21)) ([#255](https://github.com/kontourai/flow-agents/issues/255)) ([777fc50](https://github.com/kontourai/flow-agents/commit/777fc5029409f85bb3097da7f3141e3b4783a789))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Refactoring
|
|
29
|
+
|
|
30
|
+
* **flow-agents:** extract a pure seam from workflow-sidecar + add TS unit layer (ops[#22](https://github.com/kontourai/flow-agents/issues/22)) ([#257](https://github.com/kontourai/flow-agents/issues/257)) ([7af3f44](https://github.com/kontourai/flow-agents/commit/7af3f44fb1d4c05e155da9e4f6090f1a99b534ef))
|
|
31
|
+
|
|
3
32
|
## [2.1.1](https://github.com/kontourai/flow-agents/compare/v2.1.0...v2.1.1) (2026-06-29)
|
|
4
33
|
|
|
5
34
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type AnyObj = Record<string, any>;
|
|
2
|
+
export interface ClaimEvidenceItem {
|
|
3
|
+
evidenceType: string;
|
|
4
|
+
label: string;
|
|
5
|
+
execution: {
|
|
6
|
+
runner: string;
|
|
7
|
+
label: string;
|
|
8
|
+
isError: boolean;
|
|
9
|
+
exitCode: number | null;
|
|
10
|
+
} | null;
|
|
11
|
+
passing: boolean;
|
|
12
|
+
summary: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ClaimExplanation {
|
|
15
|
+
found: boolean;
|
|
16
|
+
status: string;
|
|
17
|
+
value: string;
|
|
18
|
+
claimType: string;
|
|
19
|
+
evidence: ClaimEvidenceItem[];
|
|
20
|
+
policy: {
|
|
21
|
+
id: string;
|
|
22
|
+
requiredEvidence: string[];
|
|
23
|
+
requiredMethods?: string[];
|
|
24
|
+
acceptanceCriteria: string[];
|
|
25
|
+
reviewAuthority: string;
|
|
26
|
+
} | null;
|
|
27
|
+
why: {
|
|
28
|
+
directInputs: AnyObj[];
|
|
29
|
+
leafClaims: AnyObj[];
|
|
30
|
+
diagnostics: AnyObj[];
|
|
31
|
+
transparencyGaps: AnyObj[];
|
|
32
|
+
changeRecords: AnyObj[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build a structured explanation for a specific claim.
|
|
37
|
+
* PURE: report + bundle + id in, structured explanation out.
|
|
38
|
+
* No fs, no CLI, no .flow-agents paths. Promotable to Surface #171.
|
|
39
|
+
*
|
|
40
|
+
* @param report TrustReport from buildTrustReport(bundle) — required for derived status
|
|
41
|
+
* @param bundle Raw parsed trust.bundle (BundleFile shape)
|
|
42
|
+
* @param claimId The claim id to explain
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildClaimExplanation(report: Record<string, unknown>, bundle: Record<string, unknown>, claimId: string): ClaimExplanation;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Claim explanation — a PURE projection extracted from workflow-sidecar.ts (ops#22).
|
|
2
|
+
//
|
|
3
|
+
// buildClaimExplanation is a pure function: report + bundle + id in, structured
|
|
4
|
+
// explanation out. No fs, no CLI, no .flow-agents paths, no shared module state —
|
|
5
|
+
// zero flow-agents specifics inside it, so it is unit-testable in isolation and can
|
|
6
|
+
// be lifted to Surface unchanged (issue #171). The IO command handler `claimLookup`
|
|
7
|
+
// that consumes it stays in workflow-sidecar.ts.
|
|
8
|
+
/**
|
|
9
|
+
* Build a structured explanation for a specific claim.
|
|
10
|
+
* PURE: report + bundle + id in, structured explanation out.
|
|
11
|
+
* No fs, no CLI, no .flow-agents paths. Promotable to Surface #171.
|
|
12
|
+
*
|
|
13
|
+
* @param report TrustReport from buildTrustReport(bundle) — required for derived status
|
|
14
|
+
* @param bundle Raw parsed trust.bundle (BundleFile shape)
|
|
15
|
+
* @param claimId The claim id to explain
|
|
16
|
+
*/
|
|
17
|
+
export function buildClaimExplanation(report, bundle, claimId) {
|
|
18
|
+
const reportClaims = Array.isArray(report.claims) ? report.claims : [];
|
|
19
|
+
const reportClaim = reportClaims.find((c) => c.id === claimId);
|
|
20
|
+
if (!reportClaim) {
|
|
21
|
+
return {
|
|
22
|
+
found: false,
|
|
23
|
+
status: "unknown",
|
|
24
|
+
value: "",
|
|
25
|
+
claimType: "",
|
|
26
|
+
evidence: [],
|
|
27
|
+
policy: null,
|
|
28
|
+
why: { directInputs: [], leafClaims: [], diagnostics: [], transparencyGaps: [], changeRecords: [] },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const bundleClaims = Array.isArray(bundle.claims) ? bundle.claims : [];
|
|
32
|
+
const bundleClaim = bundleClaims.find((c) => c.id === claimId) ?? reportClaim;
|
|
33
|
+
const bundlePolicies = Array.isArray(bundle.policies) ? bundle.policies : [];
|
|
34
|
+
const bundleEvidence = Array.isArray(bundle.evidence) ? bundle.evidence : [];
|
|
35
|
+
// Governing policy — follow verificationPolicyId into bundle.policies[]
|
|
36
|
+
const verificationPolicyId = typeof bundleClaim.verificationPolicyId === "string" ? bundleClaim.verificationPolicyId : undefined;
|
|
37
|
+
const rawPolicy = verificationPolicyId ? bundlePolicies.find((p) => p.id === verificationPolicyId) : undefined;
|
|
38
|
+
const policy = rawPolicy
|
|
39
|
+
? {
|
|
40
|
+
id: String(rawPolicy.id ?? ""),
|
|
41
|
+
requiredEvidence: Array.isArray(rawPolicy.requiredEvidence) ? rawPolicy.requiredEvidence : [],
|
|
42
|
+
requiredMethods: Array.isArray(rawPolicy.requiredMethods) ? rawPolicy.requiredMethods : undefined,
|
|
43
|
+
acceptanceCriteria: Array.isArray(rawPolicy.acceptanceCriteria) ? rawPolicy.acceptanceCriteria : [],
|
|
44
|
+
reviewAuthority: String(rawPolicy.reviewAuthority ?? ""),
|
|
45
|
+
}
|
|
46
|
+
: null;
|
|
47
|
+
// Evidence enhancement: pull evidence items for this claim, surface the execution block
|
|
48
|
+
const claimEvidenceItems = bundleEvidence.filter((ev) => ev && ev.claimId === claimId);
|
|
49
|
+
const evidence = claimEvidenceItems.map((ev) => {
|
|
50
|
+
const exec = ev.execution && typeof ev.execution === "object" ? ev.execution : null;
|
|
51
|
+
const execution = exec
|
|
52
|
+
? {
|
|
53
|
+
runner: String(exec.runner ?? exec.label ?? ""),
|
|
54
|
+
label: String(exec.label ?? exec.runner ?? ""),
|
|
55
|
+
isError: Boolean(exec.isError ?? (typeof exec.exitCode === "number" && exec.exitCode !== 0)),
|
|
56
|
+
exitCode: typeof exec.exitCode === "number" ? exec.exitCode : null,
|
|
57
|
+
}
|
|
58
|
+
: null;
|
|
59
|
+
return {
|
|
60
|
+
evidenceType: String(ev.evidenceType ?? ev.type ?? "unknown"),
|
|
61
|
+
label: String(ev.label ?? ev.excerptOrSummary ?? ev.sourceRef ?? ev.id ?? ""),
|
|
62
|
+
execution,
|
|
63
|
+
passing: execution ? !execution.isError : String(ev.status ?? "") !== "disputed",
|
|
64
|
+
summary: String(ev.excerptOrSummary ?? ev.summary ?? ev.label ?? ""),
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
// Drilldown: extract from report structure (report.transparencyGaps, report.changeRecords)
|
|
68
|
+
const allGaps = Array.isArray(report.transparencyGaps) ? report.transparencyGaps : [];
|
|
69
|
+
const allChanges = Array.isArray(report.changeRecords) ? report.changeRecords : [];
|
|
70
|
+
const transparencyGaps = allGaps.filter((g) => g && g.claimId === claimId);
|
|
71
|
+
const changeRecords = allChanges.filter((c) => c && c.claimId === claimId);
|
|
72
|
+
return {
|
|
73
|
+
found: true,
|
|
74
|
+
status: String(reportClaim.status ?? "unknown"),
|
|
75
|
+
value: String(bundleClaim.value ?? reportClaim.value ?? ""),
|
|
76
|
+
claimType: String(bundleClaim.claimType ?? reportClaim.claimType ?? ""),
|
|
77
|
+
evidence,
|
|
78
|
+
policy,
|
|
79
|
+
why: {
|
|
80
|
+
directInputs: [], // populated by buildDerivationDrilldown if non-leaf
|
|
81
|
+
leafClaims: [],
|
|
82
|
+
diagnostics: [],
|
|
83
|
+
transparencyGaps,
|
|
84
|
+
changeRecords,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -3,6 +3,7 @@ import * as http from "node:http";
|
|
|
3
3
|
import * as https from "node:https";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { parseArgs, flagBool, flagString } from "../lib/args.js";
|
|
6
|
+
import { telemetryDataDir as defaultTelemetryDataDir } from "../lib/local-artifact-root.js";
|
|
6
7
|
const defaultChannels = "full";
|
|
7
8
|
const sensitiveQueryKeys = new Set(["token", "api_key", "apikey", "key", "secret", "password", "auth", "authorization", "access_token"]);
|
|
8
9
|
function usage() {
|
|
@@ -46,10 +47,7 @@ function channelConfigValue(config, channel, key, fallback = "") {
|
|
|
46
47
|
}
|
|
47
48
|
function telemetryDataDir(dest) {
|
|
48
49
|
const configured = process.env.TELEMETRY_DATA_DIR;
|
|
49
|
-
|
|
50
|
-
// workspace at <dest>/.telemetry. The previous "../.telemetry" duplicated
|
|
51
|
-
// the parent-escape bug fixed in config.sh on 2026-06-11.
|
|
52
|
-
return configured ? path.resolve(dest, configured) : path.resolve(dest, ".telemetry");
|
|
50
|
+
return configured ? path.resolve(dest, configured) : defaultTelemetryDataDir(dest);
|
|
53
51
|
}
|
|
54
52
|
function deriveConsoleEndpoint(consoleUrl, explicitEndpoint) {
|
|
55
53
|
if (explicitEndpoint)
|
|
@@ -3,9 +3,11 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
|
|
6
|
+
import { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, telemetryDataDir, flowAgentsArtifactRoot, legacyFlowAgentsArtifactRoot } from "../lib/local-artifact-root.js";
|
|
6
7
|
const VALID_RESULTS = new Set(["success", "partial", "failure", "not_verified"]);
|
|
7
8
|
function telemetryDir(flags) {
|
|
8
|
-
|
|
9
|
+
const explicit = flagString(flags, "telemetry-dir") ?? process.env.TELEMETRY_DATA_DIR;
|
|
10
|
+
return explicit ? path.resolve(explicit) : telemetryDataDir();
|
|
9
11
|
}
|
|
10
12
|
function ensureSafeDir(dir) {
|
|
11
13
|
let current = path.resolve(dir);
|
|
@@ -112,7 +114,8 @@ function normalize(input, runtime, flags, fallbackSource = "flow-agents") {
|
|
|
112
114
|
function importTelemetry(argv, defaultRuntime) {
|
|
113
115
|
const { flags } = parseArgs(argv);
|
|
114
116
|
const runtime = defaultRuntime ?? flagString(flags, "runtime", "codex") ?? "codex";
|
|
115
|
-
const
|
|
117
|
+
const explicitInputDir = flagString(flags, "input-telemetry-dir");
|
|
118
|
+
const input = flagString(flags, "input-full-jsonl") ?? path.join(explicitInputDir ? path.resolve(explicitInputDir) : defaultTelemetryDirForRead(), "full.jsonl");
|
|
116
119
|
if (!input || !fs.existsSync(input))
|
|
117
120
|
throw new Error(`input telemetry file does not exist: ${input}`);
|
|
118
121
|
const dir = telemetryDir(flags);
|
|
@@ -149,7 +152,7 @@ function syncArtifacts(argv) {
|
|
|
149
152
|
const dir = telemetryDir(flags);
|
|
150
153
|
ensureSafeDir(dir);
|
|
151
154
|
const artifacts = flagList(flags, "artifact-dir");
|
|
152
|
-
const records = (artifacts.length ? artifacts : [
|
|
155
|
+
const records = (artifacts.length ? artifacts : [flowAgentsArtifactRoot(), legacyFlowAgentsArtifactRoot()]).flatMap((item) => fs.existsSync(item) ? artifactOutcomes(item, flags) : []);
|
|
153
156
|
writeJsonlUpsert(path.join(dir, "outcomes.jsonl"), records, "outcome_id");
|
|
154
157
|
if (!flagBool(flags, "quiet"))
|
|
155
158
|
console.log(`synced ${records.length} artifact outcome(s) to ${path.join(dir, "outcomes.jsonl")}`);
|
|
@@ -296,7 +299,10 @@ function markdownReport(data, groupBy) {
|
|
|
296
299
|
}
|
|
297
300
|
function report(argv) {
|
|
298
301
|
const { flags } = parseArgs(argv);
|
|
299
|
-
const
|
|
302
|
+
const requestedDirs = flagList(flags, "telemetry-dir");
|
|
303
|
+
const dirs = (requestedDirs.length ? requestedDirs.map((dir) => path.resolve(dir)) : defaultTelemetryDirsForRead()).filter(Boolean);
|
|
304
|
+
if (dirs.length === 0)
|
|
305
|
+
dirs.push(telemetryDataDir());
|
|
300
306
|
dirs.forEach(ensureSafeDir);
|
|
301
307
|
const data = reportData(dirs, flagString(flags, "group-by"));
|
|
302
308
|
const format = flagString(flags, "format", "markdown");
|
|
@@ -331,7 +337,7 @@ function registerProject(argv) {
|
|
|
331
337
|
ensureSafeDir(globalDir);
|
|
332
338
|
const repoRoot = path.resolve(flagString(flags, "repo-root", ".") ?? ".");
|
|
333
339
|
const name = flagString(flags, "name", path.basename(repoRoot)) ?? path.basename(repoRoot);
|
|
334
|
-
const record = { name, repo_root: repoRoot, artifact_dir:
|
|
340
|
+
const record = { name, repo_root: repoRoot, artifact_dir: defaultArtifactRootForRead(repoRoot), input_telemetry_dir: defaultTelemetryDirForRead(repoRoot), runtime: flagString(flags, "runtime", "codex"), repo: flagString(flags, "repo", name), agent: flagString(flags, "agent"), profile_id: flagString(flags, "profile-id"), prompt_id: flagString(flags, "prompt-id"), prompt_variant: flagString(flags, "prompt-variant"), skill_ids: flagList(flags, "skill-id"), skill_variant: flagString(flags, "skill-variant") };
|
|
335
341
|
const registryFile = path.join(globalDir, "projects.json");
|
|
336
342
|
const existing = fs.existsSync(registryFile) ? JSON.parse(fs.readFileSync(registryFile, "utf8")) : { projects: [] };
|
|
337
343
|
const projects = Array.isArray(existing) ? existing : Array.isArray(existing.projects) ? existing.projects : [];
|
|
@@ -355,7 +361,9 @@ function syncProject(project, globalDir) {
|
|
|
355
361
|
const name = String(project.name ?? project.repo ?? "project").replace(/[^a-zA-Z0-9_.-]+/g, "-") || "project";
|
|
356
362
|
const store = path.join(globalDir, "projects", name);
|
|
357
363
|
ensureSafeDir(store);
|
|
358
|
-
const
|
|
364
|
+
const repoRoot = String(project.repo_root ?? process.cwd());
|
|
365
|
+
const configuredArtifactDir = typeof project.artifact_dir === "string" ? project.artifact_dir : "";
|
|
366
|
+
const artifactDir = configuredArtifactDir && fs.existsSync(configuredArtifactDir) ? configuredArtifactDir : defaultArtifactRootForRead(repoRoot);
|
|
359
367
|
const flags = {
|
|
360
368
|
"repo": String(project.repo ?? name),
|
|
361
369
|
"runtime": String(project.runtime ?? "codex"),
|
|
@@ -374,9 +382,9 @@ function discoverProjects(root) {
|
|
|
374
382
|
if (!fs.existsSync(root))
|
|
375
383
|
return [];
|
|
376
384
|
const candidates = [root, ...fs.readdirSync(root).map((name) => path.join(root, name))];
|
|
377
|
-
return candidates.filter((candidate) => fs.existsSync(
|
|
385
|
+
return candidates.filter((candidate) => fs.existsSync(flowAgentsArtifactRoot(candidate)) || fs.existsSync(legacyFlowAgentsArtifactRoot(candidate))).map((repoRoot) => {
|
|
378
386
|
const name = path.basename(repoRoot);
|
|
379
|
-
return { name, repo: name, repo_root: repoRoot, artifact_dir:
|
|
387
|
+
return { name, repo: name, repo_root: repoRoot, artifact_dir: defaultArtifactRootForRead(repoRoot), input_telemetry_dir: defaultTelemetryDirForRead(repoRoot), runtime: "codex", skill_ids: [] };
|
|
380
388
|
});
|
|
381
389
|
}
|
|
382
390
|
function syncProjects(argv) {
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { flagBool, flagString, parseArgs } from "../lib/args.js";
|
|
5
|
+
import { defaultArtifactRootForRead } from "../lib/local-artifact-root.js";
|
|
5
6
|
const ACTIVE_STATUSES = new Set([
|
|
6
7
|
"planning",
|
|
7
8
|
"planned",
|
|
@@ -23,7 +24,7 @@ function printHelp() {
|
|
|
23
24
|
console.log("Read-only dry-run audit for local workflow artifact directories.");
|
|
24
25
|
console.log("");
|
|
25
26
|
console.log("Options:");
|
|
26
|
-
console.log(" --artifact-root <path> Local artifact root to scan (default: .flow-agents)");
|
|
27
|
+
console.log(" --artifact-root <path> Local artifact root to scan (default: .kontourai/flow-agents, with .flow-agents read fallback)");
|
|
27
28
|
console.log(" --json Print stable JSON buckets instead of text");
|
|
28
29
|
console.log(" --help Show this help");
|
|
29
30
|
console.log("");
|
|
@@ -257,7 +258,8 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
257
258
|
}
|
|
258
259
|
let result;
|
|
259
260
|
try {
|
|
260
|
-
|
|
261
|
+
const root = flagString(args.flags, "artifact-root") ? path.resolve(flagString(args.flags, "artifact-root")) : defaultArtifactRootForRead();
|
|
262
|
+
result = audit(root);
|
|
261
263
|
}
|
|
262
264
|
catch (error) {
|
|
263
265
|
console.error(`workflow-artifact-cleanup-audit: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -294,47 +294,5 @@ export declare function gateAdvisoryFix(calibration: GateCalibration, claimId: s
|
|
|
294
294
|
* @param now Optional timestamp override for deterministic tests
|
|
295
295
|
*/
|
|
296
296
|
export declare function buildGateInquiryRecords(bundle: BundleFile, blockSignal: GateBlockSignal, slug: string, expectedCriterionIds: string[], surface: SurfaceModule, now?: Date): AnyObj[];
|
|
297
|
-
export
|
|
298
|
-
|
|
299
|
-
label: string;
|
|
300
|
-
execution: {
|
|
301
|
-
runner: string;
|
|
302
|
-
label: string;
|
|
303
|
-
isError: boolean;
|
|
304
|
-
exitCode: number | null;
|
|
305
|
-
} | null;
|
|
306
|
-
passing: boolean;
|
|
307
|
-
summary: string;
|
|
308
|
-
}
|
|
309
|
-
export interface ClaimExplanation {
|
|
310
|
-
found: boolean;
|
|
311
|
-
status: string;
|
|
312
|
-
value: string;
|
|
313
|
-
claimType: string;
|
|
314
|
-
evidence: ClaimEvidenceItem[];
|
|
315
|
-
policy: {
|
|
316
|
-
id: string;
|
|
317
|
-
requiredEvidence: string[];
|
|
318
|
-
requiredMethods?: string[];
|
|
319
|
-
acceptanceCriteria: string[];
|
|
320
|
-
reviewAuthority: string;
|
|
321
|
-
} | null;
|
|
322
|
-
why: {
|
|
323
|
-
directInputs: AnyObj[];
|
|
324
|
-
leafClaims: AnyObj[];
|
|
325
|
-
diagnostics: AnyObj[];
|
|
326
|
-
transparencyGaps: AnyObj[];
|
|
327
|
-
changeRecords: AnyObj[];
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Build a structured explanation for a specific claim.
|
|
332
|
-
* PURE: report + bundle + id in, structured explanation out.
|
|
333
|
-
* No fs, no CLI, no .flow-agents paths. Promotable to Surface #171.
|
|
334
|
-
*
|
|
335
|
-
* @param report TrustReport from buildTrustReport(bundle) — required for derived status
|
|
336
|
-
* @param bundle Raw parsed trust.bundle (BundleFile shape)
|
|
337
|
-
* @param claimId The claim id to explain
|
|
338
|
-
*/
|
|
339
|
-
export declare function buildClaimExplanation(report: Record<string, unknown>, bundle: Record<string, unknown>, claimId: string): ClaimExplanation;
|
|
340
|
-
export {};
|
|
297
|
+
export { buildClaimExplanation } from "./sidecar-claim-explain.js";
|
|
298
|
+
export type { ClaimEvidenceItem, ClaimExplanation } from "./sidecar-claim-explain.js";
|
|
@@ -7,6 +7,7 @@ import { createRequire } from "node:module";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
// ADR 0016 Abstraction A: shared FlowDefinition resolver (P-a)
|
|
9
9
|
import { resolveActiveFlowStep, resolveFlowFilePath, resolvePhaseMap, resolveRouteBackPolicy } from "../lib/flow-resolver.js";
|
|
10
|
+
import { defaultArtifactRootForRead, flowAgentsArtifactRoot } from "../lib/local-artifact-root.js";
|
|
10
11
|
export const statuses = new Set(["new", "planning", "planned", "in_progress", "blocked", "verifying", "verified", "needs_decision", "not_verified", "failed", "delivered", "accepted", "archived"]);
|
|
11
12
|
export const phases = ["idea", "backlog", "pickup", "planning", "execution", "verification", "goal_fit", "evidence", "release", "learning", "done"];
|
|
12
13
|
export const checkKinds = new Set(["build", "types", "lint", "test", "security", "diff", "browser", "runtime", "policy", "external"]);
|
|
@@ -618,7 +619,7 @@ function explicitArtifactRoot(p) {
|
|
|
618
619
|
const explicit = opt(p, "artifact-dir");
|
|
619
620
|
const configuredRoot = opt(p, "artifact-root");
|
|
620
621
|
if (!explicit)
|
|
621
|
-
return path.resolve(configuredRoot
|
|
622
|
+
return configuredRoot ? path.resolve(configuredRoot) : flowAgentsArtifactRoot();
|
|
622
623
|
const dir = path.resolve(explicit);
|
|
623
624
|
if (!fs.existsSync(dir))
|
|
624
625
|
die(`artifact directory does not exist: ${dir}`);
|
|
@@ -880,7 +881,7 @@ function initSidecars(dir, slug, sourceRequest, summary, nextAction, timestamp,
|
|
|
880
881
|
});
|
|
881
882
|
}
|
|
882
883
|
function ensureSession(p) {
|
|
883
|
-
const root =
|
|
884
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : flowAgentsArtifactRoot();
|
|
884
885
|
const slug = opt(p, "task-slug") || (opt(p, "work-item") ? workItemSlug(opt(p, "work-item")) : die("--task-slug is required (or pass --work-item to derive it)"));
|
|
885
886
|
const dir = sessionDirFor(root, slug);
|
|
886
887
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -913,7 +914,7 @@ function ensureSession(p) {
|
|
|
913
914
|
return 0;
|
|
914
915
|
}
|
|
915
916
|
function current(p) {
|
|
916
|
-
const root =
|
|
917
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : defaultArtifactRootForRead();
|
|
917
918
|
const dir = currentDir(root);
|
|
918
919
|
if (!dir)
|
|
919
920
|
die("no current workflow session is recorded");
|
|
@@ -1908,7 +1909,7 @@ function assertExistingLearningValid(dir) {
|
|
|
1908
1909
|
}
|
|
1909
1910
|
}
|
|
1910
1911
|
async function dogfoodPass(p) {
|
|
1911
|
-
const root =
|
|
1912
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : defaultArtifactRootForRead();
|
|
1912
1913
|
const dir = path.resolve(opt(p, "artifact-dir") || currentDir(root) || "");
|
|
1913
1914
|
requireArtifactDirUnderRoot(dir, root);
|
|
1914
1915
|
assertExistingLearningValid(dir);
|
|
@@ -2283,7 +2284,7 @@ function loadSurfacePanelJs() {
|
|
|
2283
2284
|
return "";
|
|
2284
2285
|
}
|
|
2285
2286
|
async function renderTrustPanel(p) {
|
|
2286
|
-
const root =
|
|
2287
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : defaultArtifactRootForRead();
|
|
2287
2288
|
const dir = p.positional[0] ? artifactDirFrom(p.positional[0]) : currentDir(root);
|
|
2288
2289
|
if (!dir)
|
|
2289
2290
|
die("render-trust-panel requires a workflow dir or a recorded current session");
|
|
@@ -2482,7 +2483,7 @@ function livenessLifecycle(taskDir, slug, kind, timestamp) {
|
|
|
2482
2483
|
catch { /* best-effort; liveness is advisory and must never break the workflow */ }
|
|
2483
2484
|
}
|
|
2484
2485
|
async function liveness(p) {
|
|
2485
|
-
const root =
|
|
2486
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : flowAgentsArtifactRoot();
|
|
2486
2487
|
const action = p.positional[0] || "";
|
|
2487
2488
|
const subjectId = p.positional[1] || "";
|
|
2488
2489
|
const actor = opt(p, "actor", process.env.FLOW_AGENTS_ACTOR || "unknown");
|
|
@@ -2545,86 +2546,14 @@ async function liveness(p) {
|
|
|
2545
2546
|
die("liveness action must be one of: claim | heartbeat | release | status");
|
|
2546
2547
|
return 1;
|
|
2547
2548
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
*/
|
|
2557
|
-
export function buildClaimExplanation(report, bundle, claimId) {
|
|
2558
|
-
const reportClaims = Array.isArray(report.claims) ? report.claims : [];
|
|
2559
|
-
const reportClaim = reportClaims.find((c) => c.id === claimId);
|
|
2560
|
-
if (!reportClaim) {
|
|
2561
|
-
return {
|
|
2562
|
-
found: false,
|
|
2563
|
-
status: "unknown",
|
|
2564
|
-
value: "",
|
|
2565
|
-
claimType: "",
|
|
2566
|
-
evidence: [],
|
|
2567
|
-
policy: null,
|
|
2568
|
-
why: { directInputs: [], leafClaims: [], diagnostics: [], transparencyGaps: [], changeRecords: [] },
|
|
2569
|
-
};
|
|
2570
|
-
}
|
|
2571
|
-
const bundleClaims = Array.isArray(bundle.claims) ? bundle.claims : [];
|
|
2572
|
-
const bundleClaim = bundleClaims.find((c) => c.id === claimId) ?? reportClaim;
|
|
2573
|
-
const bundlePolicies = Array.isArray(bundle.policies) ? bundle.policies : [];
|
|
2574
|
-
const bundleEvidence = Array.isArray(bundle.evidence) ? bundle.evidence : [];
|
|
2575
|
-
// Governing policy — follow verificationPolicyId into bundle.policies[]
|
|
2576
|
-
const verificationPolicyId = typeof bundleClaim.verificationPolicyId === "string" ? bundleClaim.verificationPolicyId : undefined;
|
|
2577
|
-
const rawPolicy = verificationPolicyId ? bundlePolicies.find((p) => p.id === verificationPolicyId) : undefined;
|
|
2578
|
-
const policy = rawPolicy
|
|
2579
|
-
? {
|
|
2580
|
-
id: String(rawPolicy.id ?? ""),
|
|
2581
|
-
requiredEvidence: Array.isArray(rawPolicy.requiredEvidence) ? rawPolicy.requiredEvidence : [],
|
|
2582
|
-
requiredMethods: Array.isArray(rawPolicy.requiredMethods) ? rawPolicy.requiredMethods : undefined,
|
|
2583
|
-
acceptanceCriteria: Array.isArray(rawPolicy.acceptanceCriteria) ? rawPolicy.acceptanceCriteria : [],
|
|
2584
|
-
reviewAuthority: String(rawPolicy.reviewAuthority ?? ""),
|
|
2585
|
-
}
|
|
2586
|
-
: null;
|
|
2587
|
-
// Evidence enhancement: pull evidence items for this claim, surface the execution block
|
|
2588
|
-
const claimEvidenceItems = bundleEvidence.filter((ev) => ev && ev.claimId === claimId);
|
|
2589
|
-
const evidence = claimEvidenceItems.map((ev) => {
|
|
2590
|
-
const exec = ev.execution && typeof ev.execution === "object" ? ev.execution : null;
|
|
2591
|
-
const execution = exec
|
|
2592
|
-
? {
|
|
2593
|
-
runner: String(exec.runner ?? exec.label ?? ""),
|
|
2594
|
-
label: String(exec.label ?? exec.runner ?? ""),
|
|
2595
|
-
isError: Boolean(exec.isError ?? (typeof exec.exitCode === "number" && exec.exitCode !== 0)),
|
|
2596
|
-
exitCode: typeof exec.exitCode === "number" ? exec.exitCode : null,
|
|
2597
|
-
}
|
|
2598
|
-
: null;
|
|
2599
|
-
return {
|
|
2600
|
-
evidenceType: String(ev.evidenceType ?? ev.type ?? "unknown"),
|
|
2601
|
-
label: String(ev.label ?? ev.excerptOrSummary ?? ev.sourceRef ?? ev.id ?? ""),
|
|
2602
|
-
execution,
|
|
2603
|
-
passing: execution ? !execution.isError : String(ev.status ?? "") !== "disputed",
|
|
2604
|
-
summary: String(ev.excerptOrSummary ?? ev.summary ?? ev.label ?? ""),
|
|
2605
|
-
};
|
|
2606
|
-
});
|
|
2607
|
-
// Drilldown: extract from report structure (report.transparencyGaps, report.changeRecords)
|
|
2608
|
-
const allGaps = Array.isArray(report.transparencyGaps) ? report.transparencyGaps : [];
|
|
2609
|
-
const allChanges = Array.isArray(report.changeRecords) ? report.changeRecords : [];
|
|
2610
|
-
const transparencyGaps = allGaps.filter((g) => g && g.claimId === claimId);
|
|
2611
|
-
const changeRecords = allChanges.filter((c) => c && c.claimId === claimId);
|
|
2612
|
-
return {
|
|
2613
|
-
found: true,
|
|
2614
|
-
status: String(reportClaim.status ?? "unknown"),
|
|
2615
|
-
value: String(bundleClaim.value ?? reportClaim.value ?? ""),
|
|
2616
|
-
claimType: String(bundleClaim.claimType ?? reportClaim.claimType ?? ""),
|
|
2617
|
-
evidence,
|
|
2618
|
-
policy,
|
|
2619
|
-
why: {
|
|
2620
|
-
directInputs: [], // populated by buildDerivationDrilldown if non-leaf
|
|
2621
|
-
leafClaims: [],
|
|
2622
|
-
diagnostics: [],
|
|
2623
|
-
transparencyGaps,
|
|
2624
|
-
changeRecords,
|
|
2625
|
-
},
|
|
2626
|
-
};
|
|
2627
|
-
}
|
|
2549
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2550
|
+
// ─── Claim Lookup — pure helper (promotable to Surface #171) ─────────────────
|
|
2551
|
+
// buildClaimExplanation + its types are extracted to ./sidecar-claim-explain.ts
|
|
2552
|
+
// (ops#22): a PURE projection (report + bundle + id in, structured explanation out)
|
|
2553
|
+
// with no fs/CLI/shared state, unit-tested in isolation. Re-exported here so the
|
|
2554
|
+
// library facade (src/index.ts) and the IO `claimLookup` handler below are unchanged.
|
|
2555
|
+
export { buildClaimExplanation } from "./sidecar-claim-explain.js";
|
|
2556
|
+
import { buildClaimExplanation } from "./sidecar-claim-explain.js";
|
|
2628
2557
|
/**
|
|
2629
2558
|
* claim <id> <dir>
|
|
2630
2559
|
*
|
|
@@ -2778,7 +2707,9 @@ async function main() {
|
|
|
2778
2707
|
const p = parseArgs(process.argv.slice(2));
|
|
2779
2708
|
if (!p.command)
|
|
2780
2709
|
die("workflow-sidecar command is required");
|
|
2781
|
-
const lockRoot = ["ensure-session", "current", "dogfood-pass", "liveness"].includes(p.command)
|
|
2710
|
+
const lockRoot = ["ensure-session", "current", "dogfood-pass", "liveness"].includes(p.command)
|
|
2711
|
+
? (opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : (p.command === "ensure-session" ? flowAgentsArtifactRoot() : defaultArtifactRootForRead()))
|
|
2712
|
+
: p.command === "record-agent-event" ? explicitArtifactRoot(p) : p.command === "claim" ? (p.positional[1] ? path.resolve(p.positional[1]) : "") : p.positional[0] ? artifactDirFrom(p.positional[0]) : "";
|
|
2782
2713
|
return withLock(lockRoot, ["ensure-session", "record-agent-event", "dogfood-pass"].includes(p.command), p.command, () => {
|
|
2783
2714
|
switch (p.command) {
|
|
2784
2715
|
case "ensure-session": return ensureSession(p);
|
package/build/src/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, firstExistingPath, flowAgentsArtifactRoot, KONTOURAI_DIR, legacyFlowAgentsArtifactRoot, LEGACY_FLOW_AGENTS_DIR, legacyTelemetryDataDir, LEGACY_TELEMETRY_DIR, telemetryDataDir, } from "./lib/local-artifact-root.js";
|
|
1
2
|
export { validateTrustBundle, normalizeCheck, normalizeFinding, normalizeLearning, normalizeEvidenceRefs, validateEvidenceRef, validateLearningCorrection, loadJson, writeJson, appendJsonl, sidecarBase, writeState, statuses, phases, checkKinds, checkStatuses, verdicts, } from "./cli/workflow-sidecar.js";
|
|
2
3
|
/** Read a sidecar JSON file from a workflow artifact directory; returns `{}` if absent. */
|
|
3
4
|
export declare function readSidecar(dir: string, name: string): Record<string, any>;
|
package/build/src/index.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { loadJson as _loadJson, writeJson as _writeJson } from "./cli/workflow-sidecar.js";
|
|
18
|
+
export { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, firstExistingPath, flowAgentsArtifactRoot, KONTOURAI_DIR, legacyFlowAgentsArtifactRoot, LEGACY_FLOW_AGENTS_DIR, legacyTelemetryDataDir, LEGACY_TELEMETRY_DIR, telemetryDataDir, } from "./lib/local-artifact-root.js";
|
|
18
19
|
export {
|
|
19
20
|
// Trust-bundle (Hachure) validation — the same validator the writer uses.
|
|
20
21
|
validateTrustBundle,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const KONTOURAI_DIR = ".kontourai";
|
|
2
|
+
export declare const LEGACY_FLOW_AGENTS_DIR = ".flow-agents";
|
|
3
|
+
export declare const LEGACY_TELEMETRY_DIR = ".telemetry";
|
|
4
|
+
export declare function flowAgentsArtifactRoot(cwd?: string): string;
|
|
5
|
+
export declare function legacyFlowAgentsArtifactRoot(cwd?: string): string;
|
|
6
|
+
export declare function telemetryDataDir(cwd?: string): string;
|
|
7
|
+
export declare function legacyTelemetryDataDir(cwd?: string): string;
|
|
8
|
+
export declare function firstExistingPath(candidates: string[]): string;
|
|
9
|
+
export declare function defaultArtifactRootForRead(cwd?: string): string;
|
|
10
|
+
export declare function defaultTelemetryDirForRead(cwd?: string): string;
|
|
11
|
+
export declare function defaultTelemetryDirsForRead(cwd?: string): string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export const KONTOURAI_DIR = ".kontourai";
|
|
4
|
+
export const LEGACY_FLOW_AGENTS_DIR = ".flow-agents";
|
|
5
|
+
export const LEGACY_TELEMETRY_DIR = ".telemetry";
|
|
6
|
+
export function flowAgentsArtifactRoot(cwd = process.cwd()) {
|
|
7
|
+
return path.resolve(cwd, KONTOURAI_DIR, "flow-agents");
|
|
8
|
+
}
|
|
9
|
+
export function legacyFlowAgentsArtifactRoot(cwd = process.cwd()) {
|
|
10
|
+
return path.resolve(cwd, LEGACY_FLOW_AGENTS_DIR);
|
|
11
|
+
}
|
|
12
|
+
export function telemetryDataDir(cwd = process.cwd()) {
|
|
13
|
+
return path.resolve(cwd, KONTOURAI_DIR, "telemetry");
|
|
14
|
+
}
|
|
15
|
+
export function legacyTelemetryDataDir(cwd = process.cwd()) {
|
|
16
|
+
return path.resolve(cwd, LEGACY_TELEMETRY_DIR);
|
|
17
|
+
}
|
|
18
|
+
export function firstExistingPath(candidates) {
|
|
19
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
|
|
20
|
+
}
|
|
21
|
+
export function defaultArtifactRootForRead(cwd = process.cwd()) {
|
|
22
|
+
return firstExistingPath([flowAgentsArtifactRoot(cwd), legacyFlowAgentsArtifactRoot(cwd)]);
|
|
23
|
+
}
|
|
24
|
+
export function defaultTelemetryDirForRead(cwd = process.cwd()) {
|
|
25
|
+
return firstExistingPath([telemetryDataDir(cwd), legacyTelemetryDataDir(cwd)]);
|
|
26
|
+
}
|
|
27
|
+
export function defaultTelemetryDirsForRead(cwd = process.cwd()) {
|
|
28
|
+
const dirs = [telemetryDataDir(cwd), legacyTelemetryDataDir(cwd)];
|
|
29
|
+
return dirs.filter((dir, index) => dirs.indexOf(dir) === index && fs.existsSync(dir));
|
|
30
|
+
}
|
|
@@ -74,6 +74,7 @@ const hookFilePolicies = new Map([
|
|
|
74
74
|
["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
|
|
75
75
|
["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
|
|
76
76
|
["scripts/hooks/lib/liveness-read.js", { category: "shared hook library", requiredNeedles: ["freshHolders", "readLivenessEvents"] }],
|
|
77
|
+
["scripts/hooks/lib/local-artifact-paths.js", { category: "shared hook library", requiredNeedles: ["flowAgentsArtifactRoot", "defaultArtifactRootForRead"] }],
|
|
77
78
|
["scripts/hooks/lib/patterns.sh", { category: "shared hook library", requiredNeedles: ["_detect_secrets"] }],
|
|
78
79
|
["scripts/hooks/lib/resolve-formatter.js", { category: "shared hook library", requiredNeedles: ["resolveFormatter"] }],
|
|
79
80
|
]);
|
|
@@ -7,10 +7,9 @@ TELEMETRY_CONFIG_FILE="${TELEMETRY_CONFIG_FILE:-${TELEMETRY_DIR}/telemetry.conf}
|
|
|
7
7
|
# Defaults
|
|
8
8
|
TELEMETRY_ENABLED="${TELEMETRY_ENABLED:-true}"
|
|
9
9
|
# TELEMETRY_DIR is <workspace>/scripts/telemetry, so the workspace root is
|
|
10
|
-
# two levels up.
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
TELEMETRY_DATA_DIR="${TELEMETRY_DATA_DIR:-$(cd "${TELEMETRY_DIR}/../.." && pwd)/.telemetry}"
|
|
10
|
+
# two levels up. Local runtime telemetry defaults under .kontourai/telemetry;
|
|
11
|
+
# explicit TELEMETRY_DATA_DIR still wins.
|
|
12
|
+
TELEMETRY_DATA_DIR="${TELEMETRY_DATA_DIR:-$(cd "${TELEMETRY_DIR}/../.." && pwd)/.kontourai/telemetry}"
|
|
14
13
|
TELEMETRY_SESSION_DIR="${TELEMETRY_SESSION_DIR:-${TELEMETRY_DATA_DIR}/sessions}"
|
|
15
14
|
TELEMETRY_ENRICH_SYSTEM="${TELEMETRY_ENRICH_SYSTEM:-true}"
|
|
16
15
|
TELEMETRY_ENRICH_WORKSPACE="${TELEMETRY_ENRICH_WORKSPACE:-true}"
|