@archme/cli 0.16.0 → 0.17.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/cli.js +243 -126
- package/package.json +1 -1
- package/sandbox-image.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13691,6 +13691,89 @@ var init_repoContainer = __esm({
|
|
|
13691
13691
|
}
|
|
13692
13692
|
});
|
|
13693
13693
|
|
|
13694
|
+
// ../shared/seedArchitecture.ts
|
|
13695
|
+
function seedArchitectureFromRows(rows) {
|
|
13696
|
+
const components = [];
|
|
13697
|
+
const stories = [];
|
|
13698
|
+
for (const row of rows) {
|
|
13699
|
+
const compPath = componentPathFromFilePath(row.path);
|
|
13700
|
+
if (compPath !== null) {
|
|
13701
|
+
const parsed = tryParse(row.content);
|
|
13702
|
+
components.push({
|
|
13703
|
+
path: compPath,
|
|
13704
|
+
anchors: parsed?.anchors ?? [],
|
|
13705
|
+
unanchored: parsed?.unanchored === true ? true : void 0
|
|
13706
|
+
});
|
|
13707
|
+
continue;
|
|
13708
|
+
}
|
|
13709
|
+
const scope = storyScopeFromFilePath(row.path);
|
|
13710
|
+
if (scope !== null) {
|
|
13711
|
+
const parsed = tryParse(row.content);
|
|
13712
|
+
if (!parsed) continue;
|
|
13713
|
+
collectSeedStories(parsed, scope, row.path, stories);
|
|
13714
|
+
}
|
|
13715
|
+
}
|
|
13716
|
+
return { components, stories };
|
|
13717
|
+
}
|
|
13718
|
+
function tryParse(s) {
|
|
13719
|
+
try {
|
|
13720
|
+
return JSON.parse(s);
|
|
13721
|
+
} catch {
|
|
13722
|
+
return null;
|
|
13723
|
+
}
|
|
13724
|
+
}
|
|
13725
|
+
function componentPathFromFilePath(p) {
|
|
13726
|
+
if (!p.endsWith("/component.json")) return null;
|
|
13727
|
+
const inner = p.slice(0, -"/component.json".length);
|
|
13728
|
+
if (inner.length === 0) return null;
|
|
13729
|
+
const segments = inner.split("/");
|
|
13730
|
+
if (segments.length % 2 !== 0) return null;
|
|
13731
|
+
const ids = [];
|
|
13732
|
+
for (let i2 = 0; i2 < segments.length; i2 += 2) {
|
|
13733
|
+
if (segments[i2] !== "components") return null;
|
|
13734
|
+
const id = segments[i2 + 1];
|
|
13735
|
+
if (!id) return null;
|
|
13736
|
+
ids.push(id);
|
|
13737
|
+
}
|
|
13738
|
+
return `root.${ids.join(".")}`;
|
|
13739
|
+
}
|
|
13740
|
+
function storyScopeFromFilePath(p) {
|
|
13741
|
+
if (!STORY_PATH_RE.test(p)) return null;
|
|
13742
|
+
const prefix = p.slice(0, p.lastIndexOf("stories/"));
|
|
13743
|
+
if (prefix.length === 0) return [];
|
|
13744
|
+
const segments = prefix.split("/").filter(Boolean);
|
|
13745
|
+
const ids = [];
|
|
13746
|
+
for (let i2 = 0; i2 < segments.length; i2 += 2) {
|
|
13747
|
+
if (segments[i2] !== "components") return null;
|
|
13748
|
+
const id = segments[i2 + 1];
|
|
13749
|
+
if (!id) return null;
|
|
13750
|
+
ids.push(id);
|
|
13751
|
+
}
|
|
13752
|
+
return ids;
|
|
13753
|
+
}
|
|
13754
|
+
function collectSeedStories(raw, scope, fileFallbackId, out, parentId) {
|
|
13755
|
+
const id = raw.id ?? fileFallbackId;
|
|
13756
|
+
out.push({
|
|
13757
|
+
id,
|
|
13758
|
+
title: raw.title ?? id,
|
|
13759
|
+
sequence: raw.sequence ?? "",
|
|
13760
|
+
draft: raw.draft === true ? true : void 0,
|
|
13761
|
+
scope,
|
|
13762
|
+
parentId
|
|
13763
|
+
});
|
|
13764
|
+
if (Array.isArray(raw.userStories)) {
|
|
13765
|
+
for (const child of raw.userStories) {
|
|
13766
|
+
collectSeedStories(child, scope, fileFallbackId, out, id);
|
|
13767
|
+
}
|
|
13768
|
+
}
|
|
13769
|
+
}
|
|
13770
|
+
var STORY_PATH_RE;
|
|
13771
|
+
var init_seedArchitecture = __esm({
|
|
13772
|
+
"../shared/seedArchitecture.ts"() {
|
|
13773
|
+
STORY_PATH_RE = /^(?:components\/[^/]+\/)*stories\/[^/]+\.json$/;
|
|
13774
|
+
}
|
|
13775
|
+
});
|
|
13776
|
+
|
|
13694
13777
|
// src/scan/affected.ts
|
|
13695
13778
|
function normalizePath(p) {
|
|
13696
13779
|
return p.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/").replace(/\/$/, "");
|
|
@@ -13904,42 +13987,6 @@ function formatAffectedSeed(result, removedSymbols = [], allStories) {
|
|
|
13904
13987
|
}
|
|
13905
13988
|
return lines.join("\n");
|
|
13906
13989
|
}
|
|
13907
|
-
function tryParse(s) {
|
|
13908
|
-
try {
|
|
13909
|
-
return JSON.parse(s);
|
|
13910
|
-
} catch {
|
|
13911
|
-
return null;
|
|
13912
|
-
}
|
|
13913
|
-
}
|
|
13914
|
-
function componentPathFromFilePath(p) {
|
|
13915
|
-
if (!p.endsWith("/component.json")) return null;
|
|
13916
|
-
const inner = p.slice(0, -"/component.json".length);
|
|
13917
|
-
if (inner.length === 0) return null;
|
|
13918
|
-
const segments = inner.split("/");
|
|
13919
|
-
if (segments.length % 2 !== 0) return null;
|
|
13920
|
-
const ids = [];
|
|
13921
|
-
for (let i2 = 0; i2 < segments.length; i2 += 2) {
|
|
13922
|
-
if (segments[i2] !== "components") return null;
|
|
13923
|
-
const id = segments[i2 + 1];
|
|
13924
|
-
if (!id) return null;
|
|
13925
|
-
ids.push(id);
|
|
13926
|
-
}
|
|
13927
|
-
return `root.${ids.join(".")}`;
|
|
13928
|
-
}
|
|
13929
|
-
function storyScopeFromFilePath(p) {
|
|
13930
|
-
if (!STORY_PATH_RE.test(p)) return null;
|
|
13931
|
-
const prefix = p.slice(0, p.lastIndexOf("stories/"));
|
|
13932
|
-
if (prefix.length === 0) return [];
|
|
13933
|
-
const segments = prefix.split("/").filter(Boolean);
|
|
13934
|
-
const ids = [];
|
|
13935
|
-
for (let i2 = 0; i2 < segments.length; i2 += 2) {
|
|
13936
|
-
if (segments[i2] !== "components") return null;
|
|
13937
|
-
const id = segments[i2 + 1];
|
|
13938
|
-
if (!id) return null;
|
|
13939
|
-
ids.push(id);
|
|
13940
|
-
}
|
|
13941
|
-
return ids;
|
|
13942
|
-
}
|
|
13943
13990
|
async function loadArchitectureForSeeding(supabase, projectId, branch) {
|
|
13944
13991
|
const PAGE_SIZE2 = 1e3;
|
|
13945
13992
|
const rows = [];
|
|
@@ -13950,54 +13997,18 @@ async function loadArchitectureForSeeding(supabase, projectId, branch) {
|
|
|
13950
13997
|
rows.push(...data);
|
|
13951
13998
|
if (data.length < PAGE_SIZE2) break;
|
|
13952
13999
|
}
|
|
13953
|
-
|
|
13954
|
-
const stories = [];
|
|
13955
|
-
for (const row of rows) {
|
|
13956
|
-
const compPath = componentPathFromFilePath(row.path);
|
|
13957
|
-
if (compPath !== null) {
|
|
13958
|
-
const parsed = tryParse(row.content);
|
|
13959
|
-
components.push({
|
|
13960
|
-
path: compPath,
|
|
13961
|
-
anchors: parsed?.anchors ?? [],
|
|
13962
|
-
unanchored: parsed?.unanchored === true ? true : void 0
|
|
13963
|
-
});
|
|
13964
|
-
continue;
|
|
13965
|
-
}
|
|
13966
|
-
const scope = storyScopeFromFilePath(row.path);
|
|
13967
|
-
if (scope !== null) {
|
|
13968
|
-
const parsed = tryParse(row.content);
|
|
13969
|
-
if (!parsed) continue;
|
|
13970
|
-
collectSeedStories(parsed, scope, row.path, stories);
|
|
13971
|
-
}
|
|
13972
|
-
}
|
|
13973
|
-
return { components, stories };
|
|
14000
|
+
return seedArchitectureFromRows(rows);
|
|
13974
14001
|
}
|
|
13975
|
-
|
|
13976
|
-
const id = raw.id ?? fileFallbackId;
|
|
13977
|
-
out.push({
|
|
13978
|
-
id,
|
|
13979
|
-
title: raw.title ?? id,
|
|
13980
|
-
sequence: raw.sequence ?? "",
|
|
13981
|
-
draft: raw.draft === true ? true : void 0,
|
|
13982
|
-
scope,
|
|
13983
|
-
parentId
|
|
13984
|
-
});
|
|
13985
|
-
if (Array.isArray(raw.userStories)) {
|
|
13986
|
-
for (const child of raw.userStories) {
|
|
13987
|
-
collectSeedStories(child, scope, fileFallbackId, out, id);
|
|
13988
|
-
}
|
|
13989
|
-
}
|
|
13990
|
-
}
|
|
13991
|
-
var MESSAGE_RE, PARTICIPANT_RE, DISPLAY_FILE_CAP, STORY_PATH_RE;
|
|
14002
|
+
var MESSAGE_RE, PARTICIPANT_RE, DISPLAY_FILE_CAP;
|
|
13992
14003
|
var init_affected = __esm({
|
|
13993
14004
|
"src/scan/affected.ts"() {
|
|
13994
14005
|
"use strict";
|
|
13995
14006
|
init_esm3();
|
|
13996
14007
|
init_repoContainer();
|
|
14008
|
+
init_seedArchitecture();
|
|
13997
14009
|
MESSAGE_RE = /^\s*(\{[^}]+\}|\S+?)\s*(--?>>|--?>|->|-->|-x|-\))\s*(\{[^}]+\}|\S+?)\s*:\s*(.*)$/;
|
|
13998
14010
|
PARTICIPANT_RE = /^\s*participant\s+(\S+?)(?:\s+as\s+.+)?$/;
|
|
13999
14011
|
DISPLAY_FILE_CAP = 200;
|
|
14000
|
-
STORY_PATH_RE = /^(?:components\/[^/]+\/)*stories\/[^/]+\.json$/;
|
|
14001
14012
|
}
|
|
14002
14013
|
});
|
|
14003
14014
|
|
|
@@ -16251,10 +16262,11 @@ function buildBugReportGuidance(bugsFolderId) {
|
|
|
16251
16262
|
|
|
16252
16263
|
${folderMechanic}`;
|
|
16253
16264
|
}
|
|
16254
|
-
var BUGS_FOLDER_TITLE, BUG_REPORT_CORE;
|
|
16265
|
+
var BUGS_FOLDER_TITLE, BUGS_FOLDER_DESCRIPTION, BUG_REPORT_CORE;
|
|
16255
16266
|
var init_bugReport = __esm({
|
|
16256
16267
|
"../shared/bugReport.ts"() {
|
|
16257
16268
|
BUGS_FOLDER_TITLE = "Bugs found by ArchMe";
|
|
16269
|
+
BUGS_FOLDER_DESCRIPTION = "Bugs noticed by scan/diff agents in the code while updating the architecture. Prose-only proposals; humans triage.";
|
|
16258
16270
|
BUG_REPORT_CORE = "**Bugs you happen to notice.** Don't hunt for bugs \u2014 stay on the architecture task \u2014 but don't ignore an obvious, real one that surfaces while you read the code (a null-deref, a missing `await`, an unhandled error path, a broken invariant). Have any sub-agents you dispatch report a suspect back to you (a one-line `possible bug: <what> at <file:line>`) rather than filing it themselves. When suspects come back, dispatch ONE bug-validation sub-agent \u2014 it reads code and calls `create_proposal`, exactly like the build sub-agent, and never modifies the user's code \u2014 and have it: verify each suspect against the code, and for every one it confirms as a real defect, FILE it itself as a prose-only proposal (`create_proposal` with `kind: 'proposal'`, `stage: 'backlog'`, an empty graph delta, and a one-paragraph `body`: what is wrong, where by file:line or symbol, and why), `about` the component it lives in (`{ component: '<root.\u2026 path>' }` \u2014 find_components_for_paths maps the file to it), else the user story whose flow it breaks (`{ story: '<id>' }`). It skips anything it can't confirm, and searches the folder by title first to skip anything already logged. Keep the bar high \u2014 obvious, confirmed defects only, a few per run at most, never speculation, style, or a design opinion; record, never fix. Logging a bug is orthogonal to your architecture task and does NOT replace your completion signal \u2014 still call `update_architecture` or `report_no_change` exactly as you otherwise would.";
|
|
16259
16271
|
}
|
|
16260
16272
|
});
|
|
@@ -16685,7 +16697,11 @@ var init_validationCodes = __esm({
|
|
|
16685
16697
|
WALKTHROUGH_COVERAGE_CHAPTER_NOT_COVERED: "walkthrough.coverage.chapter-not-covered",
|
|
16686
16698
|
/** The delta added, removed or modified a component no step names. */
|
|
16687
16699
|
WALKTHROUGH_COVERAGE_COMPONENT_NOT_COVERED: "walkthrough.coverage.component-not-covered",
|
|
16688
|
-
/**
|
|
16700
|
+
/**
|
|
16701
|
+
* The delta changed a connection no step names, joins, or draws.
|
|
16702
|
+
* @deprecated Retired: connections show up through the stories that draw
|
|
16703
|
+
* them, so coverage no longer requires them.
|
|
16704
|
+
*/
|
|
16689
16705
|
WALKTHROUGH_COVERAGE_CONNECTION_NOT_COVERED: "walkthrough.coverage.connection-not-covered",
|
|
16690
16706
|
// ──────────────────────────────────────────────────────────────────────────
|
|
16691
16707
|
// mcp — request-level rules applied before any tool handler runs
|
|
@@ -16742,7 +16758,7 @@ var init_walkthroughGuidance = __esm({
|
|
|
16742
16758
|
init_walkthrough();
|
|
16743
16759
|
WALKTHROUGH_GUIDANCE = `A walkthrough breaks a change into bite-sized chunks a reviewer can step through. Each step names the elements it is about; the review surface filters the sequence diagram and the component graph down to just those, so the reader sees one chunk at a time instead of a forty-element diff at once.
|
|
16744
16760
|
|
|
16745
|
-
- **Every step must name what it is about** \u2014 the stories, chapters, components and/or connections it covers. A step with only prose is rejected: there is nothing to filter the diagrams by, and an explanation a reader cannot check against the change is exactly what this view exists to avoid.
|
|
16761
|
+
- **Every step must name what it is about** \u2014 the stories, chapters, components and/or connections it covers. A step with only prose is rejected: there is nothing to filter the diagrams by, and an explanation a reader cannot check against the change is exactly what this view exists to avoid. Name a connection inside a component as \`<component path>::<id>\` (\`daemon::session_auth->key_account\`); a root connection by its plain id.
|
|
16746
16762
|
- **Steps are in reading order, not build order.** This is not an implementation plan and not a restatement of your sequencing section \u2014 start where a reviewer should start, and put each step where it makes the next one make sense.
|
|
16747
16763
|
- **Group by idea.** One step is one coherent change, usually 3\u20138 elements. A one-element step is fine and often valuable: an independent fix that would otherwise be lost among the rest. Aim for a handful of steps; the cap is ${MAX_WALKTHROUGH_STEPS}.
|
|
16748
16764
|
- **Titles are terse** \u2014 a handle, not a summary. Under ${MAX_STEP_TITLE_CHARS} characters. \`Markers move per-repo\`, not \`This step explains how the source marker moves onto a per-repo table\`.
|
|
@@ -16754,7 +16770,7 @@ var init_walkthroughGuidance = __esm({
|
|
|
16754
16770
|
- Don't use the change's OWN new vocabulary undefined. If the step introduces a term, say what it means in the same breath or use plain words.
|
|
16755
16771
|
- Bad: \`A correctness item, not a phase-3 nicety: anchors are repo-relative paths, so two repos with the same conventional layout collide silently and seed stories from the wrong one.\`
|
|
16756
16772
|
- Good: \`With two repos in one project a path like src/index.ts exists in both, so looking up which component owns a changed file can return the wrong repo's. Each repo now records the container its code lives under, and lookups search only that subtree.\`
|
|
16757
|
-
- **Cover every change** \u2014 every story
|
|
16773
|
+
- **Cover every change** \u2014 every story and component the change touches \u2014 or \`set_walkthrough\` rejects the walkthrough and lists what is missing. Guided mode walks the steps and nothing after them, so anything no step reaches is a change the reviewer never sees. Connections need no step of their own: they show up through the stories that draw them.
|
|
16758
16774
|
- **Skip it for a small change.** A handful of elements needs no walkthrough; omit it rather than writing a step per element.
|
|
16759
16775
|
- **Prefer CHAPTERS to narrow a diagram.** If a story's sequence carries \`%% chapter: <id> | <Label>\` markers, name the ones your step covers as \`chapters: [{ story, chapter }]\`, using the **id**, never the label \u2014 ids are stable across a rename, labels are not. A chapter is a page break the story's author already wrote, so it is sharper than naming components, where one hub the step mentions decides the whole view.`;
|
|
16760
16776
|
}
|
|
@@ -16865,12 +16881,11 @@ async function ensureBugsFolder(client, projectId) {
|
|
|
16865
16881
|
return null;
|
|
16866
16882
|
}
|
|
16867
16883
|
}
|
|
16868
|
-
var
|
|
16884
|
+
var cache;
|
|
16869
16885
|
var init_bugsFolder = __esm({
|
|
16870
16886
|
"src/daemon/bugsFolder.ts"() {
|
|
16871
16887
|
"use strict";
|
|
16872
16888
|
init_bugReport();
|
|
16873
|
-
BUGS_FOLDER_DESCRIPTION = "Bugs noticed by scan/diff agents in the code while updating the architecture. Prose-only proposals; humans triage.";
|
|
16874
16889
|
cache = /* @__PURE__ */ new Map();
|
|
16875
16890
|
}
|
|
16876
16891
|
});
|
|
@@ -19496,7 +19511,7 @@ var init_setting = __esm({
|
|
|
19496
19511
|
}
|
|
19497
19512
|
});
|
|
19498
19513
|
|
|
19499
|
-
//
|
|
19514
|
+
// ../shared/architectureCounts.ts
|
|
19500
19515
|
function countFromPaths(paths) {
|
|
19501
19516
|
let components = 0;
|
|
19502
19517
|
let stories = 0;
|
|
@@ -19510,6 +19525,12 @@ function countFromPaths(paths) {
|
|
|
19510
19525
|
}
|
|
19511
19526
|
return { components, stories, rootStories };
|
|
19512
19527
|
}
|
|
19528
|
+
var init_architectureCounts = __esm({
|
|
19529
|
+
"../shared/architectureCounts.ts"() {
|
|
19530
|
+
}
|
|
19531
|
+
});
|
|
19532
|
+
|
|
19533
|
+
// src/scan/architectureCounts.ts
|
|
19513
19534
|
async function countArchitecture(supabase, projectId, branch) {
|
|
19514
19535
|
const PAGE_SIZE2 = 1e3;
|
|
19515
19536
|
const paths = [];
|
|
@@ -19529,9 +19550,10 @@ function firstScanGate(counts) {
|
|
|
19529
19550
|
reason: `first scan incomplete: the branch holds ${counts.components} component(s) and ${counts.rootStories} root story(ies); needs at least one of each`
|
|
19530
19551
|
};
|
|
19531
19552
|
}
|
|
19532
|
-
var
|
|
19553
|
+
var init_architectureCounts2 = __esm({
|
|
19533
19554
|
"src/scan/architectureCounts.ts"() {
|
|
19534
19555
|
"use strict";
|
|
19556
|
+
init_architectureCounts();
|
|
19535
19557
|
}
|
|
19536
19558
|
});
|
|
19537
19559
|
|
|
@@ -19646,6 +19668,103 @@ var init_platformDevices = __esm({
|
|
|
19646
19668
|
}
|
|
19647
19669
|
});
|
|
19648
19670
|
|
|
19671
|
+
// src/daemon/jobReads.ts
|
|
19672
|
+
async function lookupProjectSlug(client, projectId) {
|
|
19673
|
+
const { data, error } = await client.from("projects").select("slug, organizations(slug)").eq("id", projectId).single();
|
|
19674
|
+
if (error || !data) throw new Error(`Project ${projectId} not found: ${error?.message}`);
|
|
19675
|
+
const orgSlug = data.organizations?.slug;
|
|
19676
|
+
if (!orgSlug) throw new Error(`Project ${projectId} has no org slug`);
|
|
19677
|
+
return `${orgSlug}/${data.slug}`;
|
|
19678
|
+
}
|
|
19679
|
+
async function detectWritesByPhase(client, projectId, archmeBranch, jobId) {
|
|
19680
|
+
const { data, error } = await client.from("commits").select("structural_changeset").eq("project_id", projectId).eq("branch", archmeBranch).eq("metadata->source->>job_id", jobId);
|
|
19681
|
+
if (error) {
|
|
19682
|
+
console.error("commits lookup error:", error.message);
|
|
19683
|
+
return { total: 0, diffWrites: 0, structuralWrites: 0 };
|
|
19684
|
+
}
|
|
19685
|
+
let diffWrites = 0;
|
|
19686
|
+
let structuralWrites = 0;
|
|
19687
|
+
for (const row of data ?? []) {
|
|
19688
|
+
if (row.structural_changeset == null) diffWrites++;
|
|
19689
|
+
else structuralWrites++;
|
|
19690
|
+
}
|
|
19691
|
+
return { total: diffWrites + structuralWrites, diffWrites, structuralWrites };
|
|
19692
|
+
}
|
|
19693
|
+
async function detectMcpActivity(client, jobId) {
|
|
19694
|
+
const { data, error } = await client.from("archme_jobs").select("meaningful_tool_calls, no_change_reason").eq("id", jobId).maybeSingle();
|
|
19695
|
+
if (error) {
|
|
19696
|
+
console.error("mcp_activity lookup error:", error.message);
|
|
19697
|
+
return { reads: void 0, noChange: false };
|
|
19698
|
+
}
|
|
19699
|
+
if (!data) return { reads: void 0, noChange: false };
|
|
19700
|
+
const row = data;
|
|
19701
|
+
return { reads: row.meaningful_tool_calls, noChange: row.no_change_reason != null };
|
|
19702
|
+
}
|
|
19703
|
+
function sessionJobReads(client, job) {
|
|
19704
|
+
return {
|
|
19705
|
+
projectSlug: () => lookupProjectSlug(client, job.project_id),
|
|
19706
|
+
marker: (repo) => readBranchMarker(client, job.project_id, job.archme_branch, repo),
|
|
19707
|
+
repoMappings: (log) => loadRepoMappings(client, job.project_id, log),
|
|
19708
|
+
seedArchitecture: () => loadArchitectureForSeeding(client, job.project_id, job.archme_branch),
|
|
19709
|
+
bugsFolderId: () => ensureBugsFolder(client, job.project_id),
|
|
19710
|
+
writesByPhase: () => detectWritesByPhase(client, job.project_id, job.archme_branch, job.id),
|
|
19711
|
+
mcpActivity: () => detectMcpActivity(client, job.id),
|
|
19712
|
+
architectureCounts: () => countArchitecture(client, job.project_id, job.archme_branch)
|
|
19713
|
+
};
|
|
19714
|
+
}
|
|
19715
|
+
function hostedJobReads(post) {
|
|
19716
|
+
let prefetch = null;
|
|
19717
|
+
let outcome = null;
|
|
19718
|
+
const pre = () => prefetch ??= post("prefetch");
|
|
19719
|
+
const out = () => outcome ??= post("outcome");
|
|
19720
|
+
return {
|
|
19721
|
+
projectSlug: async () => {
|
|
19722
|
+
const slug = (await pre()).project_slug;
|
|
19723
|
+
if (typeof slug !== "string") throw new Error("job-context returned no project slug");
|
|
19724
|
+
return slug;
|
|
19725
|
+
},
|
|
19726
|
+
marker: async () => (await pre()).marker,
|
|
19727
|
+
repoMappings: async (log) => {
|
|
19728
|
+
const mappings = (await pre()).repo_mappings;
|
|
19729
|
+
if (mappings === null) log("repo mappings unreadable (proceeding without repo scoping)");
|
|
19730
|
+
return mappings;
|
|
19731
|
+
},
|
|
19732
|
+
seedArchitecture: async () => {
|
|
19733
|
+
const p = await pre();
|
|
19734
|
+
if (!p.seed_architecture) throw new Error(p.seed_error ?? "seed architecture unavailable");
|
|
19735
|
+
return p.seed_architecture;
|
|
19736
|
+
},
|
|
19737
|
+
bugsFolderId: async () => (await pre()).bugs_folder_id,
|
|
19738
|
+
// Fails closed, unlike the session readers: a platform job has no session read to fall back on.
|
|
19739
|
+
writesByPhase: async () => {
|
|
19740
|
+
const { total, diff, structural } = (await out()).writes;
|
|
19741
|
+
return { total, diffWrites: diff, structuralWrites: structural };
|
|
19742
|
+
},
|
|
19743
|
+
mcpActivity: async () => {
|
|
19744
|
+
const { reads, no_change } = (await out()).mcp;
|
|
19745
|
+
return { reads: reads ?? void 0, noChange: no_change };
|
|
19746
|
+
},
|
|
19747
|
+
architectureCounts: async () => {
|
|
19748
|
+
const counts = (await out()).architecture_counts;
|
|
19749
|
+
if (!counts) throw new Error("job-context returned no architecture counts");
|
|
19750
|
+
return counts;
|
|
19751
|
+
}
|
|
19752
|
+
};
|
|
19753
|
+
}
|
|
19754
|
+
function jobReadsFor(deps, job) {
|
|
19755
|
+
return deps.isPlatformDevice ? hostedJobReads(deps.post) : sessionJobReads(deps.client, job);
|
|
19756
|
+
}
|
|
19757
|
+
var init_jobReads = __esm({
|
|
19758
|
+
"src/daemon/jobReads.ts"() {
|
|
19759
|
+
"use strict";
|
|
19760
|
+
init_architectureCounts2();
|
|
19761
|
+
init_affected();
|
|
19762
|
+
init_bugsFolder();
|
|
19763
|
+
init_branchMarker();
|
|
19764
|
+
init_repoMappings();
|
|
19765
|
+
}
|
|
19766
|
+
});
|
|
19767
|
+
|
|
19649
19768
|
// src/daemon/sandboxJob/result.ts
|
|
19650
19769
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
19651
19770
|
function isValidNonce(nonce) {
|
|
@@ -22248,9 +22367,9 @@ var init_run_scan = __esm({
|
|
|
22248
22367
|
init_transcript_signals();
|
|
22249
22368
|
init_report();
|
|
22250
22369
|
init_github_remote();
|
|
22251
|
-
|
|
22370
|
+
init_architectureCounts2();
|
|
22252
22371
|
init_claimDefaultProject();
|
|
22253
|
-
|
|
22372
|
+
init_architectureCounts2();
|
|
22254
22373
|
BOOTSTRAP_HEADLESS = `Run the \`scan\` MCP prompt now and follow it to completion. This is a non-interactive automated run \u2014 skip any steps that pause for user confirmation (e.g. "does this hierarchy look right?"). Proceed with your best-judgement decisions rather than waiting for human input.`;
|
|
22255
22374
|
BOOTSTRAP_INTERACTIVE = `Run the \`scan\` MCP prompt now and follow it to completion. The user is at an interactive terminal \u2014 ask them when the prompt says to, and wait for their answer.
|
|
22256
22375
|
|
|
@@ -22739,13 +22858,6 @@ async function routeShutdownInterrupt(id, cancelled2, fail2, exitCode) {
|
|
|
22739
22858
|
console.error(` shutdown retry-route failed for ${id}: ${e instanceof Error ? e.message : e}`);
|
|
22740
22859
|
}
|
|
22741
22860
|
}
|
|
22742
|
-
async function lookupProjectSlug(client, projectId) {
|
|
22743
|
-
const { data, error } = await client.from("projects").select("slug, organizations(slug)").eq("id", projectId).single();
|
|
22744
|
-
if (error || !data) throw new Error(`Project ${projectId} not found: ${error?.message}`);
|
|
22745
|
-
const orgSlug = data.organizations?.slug;
|
|
22746
|
-
if (!orgSlug) throw new Error(`Project ${projectId} has no org slug`);
|
|
22747
|
-
return `${orgSlug}/${data.slug}`;
|
|
22748
|
-
}
|
|
22749
22861
|
function cloneDirFor(baseDir, owner, name) {
|
|
22750
22862
|
return join25(baseDir, owner, name);
|
|
22751
22863
|
}
|
|
@@ -23221,30 +23333,6 @@ async function rpcComplete(ctx, jobId, deviceId, summary2, kind, changed, metric
|
|
|
23221
23333
|
function affectedFieldForComplete(sourceMode, affected) {
|
|
23222
23334
|
return sourceMode === "pr_update" && affected ? affected : void 0;
|
|
23223
23335
|
}
|
|
23224
|
-
async function detectWritesByPhase(client, projectId, archmeBranch, jobId) {
|
|
23225
|
-
const { data, error } = await client.from("commits").select("structural_changeset").eq("project_id", projectId).eq("branch", archmeBranch).eq("metadata->source->>job_id", jobId);
|
|
23226
|
-
if (error) {
|
|
23227
|
-
console.error("commits lookup error:", error.message);
|
|
23228
|
-
return { total: 0, diffWrites: 0, structuralWrites: 0 };
|
|
23229
|
-
}
|
|
23230
|
-
let diffWrites = 0;
|
|
23231
|
-
let structuralWrites = 0;
|
|
23232
|
-
for (const row of data ?? []) {
|
|
23233
|
-
if (row.structural_changeset == null) diffWrites++;
|
|
23234
|
-
else structuralWrites++;
|
|
23235
|
-
}
|
|
23236
|
-
return { total: diffWrites + structuralWrites, diffWrites, structuralWrites };
|
|
23237
|
-
}
|
|
23238
|
-
async function detectMcpActivity(client, jobId) {
|
|
23239
|
-
const { data, error } = await client.from("archme_jobs").select("meaningful_tool_calls, no_change_reason").eq("id", jobId).maybeSingle();
|
|
23240
|
-
if (error) {
|
|
23241
|
-
console.error("mcp_activity lookup error:", error.message);
|
|
23242
|
-
return { reads: void 0, noChange: false };
|
|
23243
|
-
}
|
|
23244
|
-
if (!data) return { reads: void 0, noChange: false };
|
|
23245
|
-
const row = data;
|
|
23246
|
-
return { reads: row.meaningful_tool_calls, noChange: row.no_change_reason != null };
|
|
23247
|
-
}
|
|
23248
23336
|
function setLocalPause(untilMs) {
|
|
23249
23337
|
if (!Number.isFinite(untilMs) || untilMs <= Date.now()) return;
|
|
23250
23338
|
if (pauseUntilMs != null && pauseUntilMs >= untilMs) return;
|
|
@@ -23518,11 +23606,36 @@ async function setupDigestMcp(ctx, job, deviceId) {
|
|
|
23518
23606
|
return {};
|
|
23519
23607
|
}
|
|
23520
23608
|
}
|
|
23609
|
+
function readsFor(ctx, job, deviceId) {
|
|
23610
|
+
return jobReadsFor(
|
|
23611
|
+
{ client: ctx.supabase, post: (part) => postJobContext(ctx, job.id, deviceId, part), isPlatformDevice: platformDevices.has(deviceId) },
|
|
23612
|
+
job
|
|
23613
|
+
);
|
|
23614
|
+
}
|
|
23615
|
+
async function postJobContext(ctx, jobId, deviceId, part) {
|
|
23616
|
+
const response = await fetchWithAuthRetry(
|
|
23617
|
+
"job-context",
|
|
23618
|
+
() => {
|
|
23619
|
+
const accessToken = ctx.auth.getAccessToken();
|
|
23620
|
+
if (!accessToken) throw new Error("daemon is not signed in with a registered device");
|
|
23621
|
+
return fetch(`${ctx.supabaseUrl.replace(/\/$/, "")}/functions/v1/agents-device/job-context`, {
|
|
23622
|
+
method: "POST",
|
|
23623
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}` },
|
|
23624
|
+
body: JSON.stringify({ device_id: deviceId, job_id: jobId, part })
|
|
23625
|
+
});
|
|
23626
|
+
},
|
|
23627
|
+
ctx.reLogin
|
|
23628
|
+
);
|
|
23629
|
+
const body = await parseJsonResponse(response);
|
|
23630
|
+
if (!response.ok) throw new Error(`job-context ${part} failed: ${responseError(body, `status ${response.status}`)}`);
|
|
23631
|
+
return body;
|
|
23632
|
+
}
|
|
23521
23633
|
async function prefetchJobContext(ctx, job, deviceId, repoCredentials = getRepoCredentials) {
|
|
23522
23634
|
const log = (msg) => console.log(`[${job.id}] ${msg}`);
|
|
23523
23635
|
const firstScan = job.job_kind === "first_scan";
|
|
23524
23636
|
assertLocalJobShape(firstScan ? { ...job, after_sha: job.scan_ref ?? "" } : job);
|
|
23525
|
-
const
|
|
23637
|
+
const reads = readsFor(ctx, job, deviceId);
|
|
23638
|
+
const projectSlug = await reads.projectSlug();
|
|
23526
23639
|
const jobToken = await mintJobToken(ctx, job.id, deviceId);
|
|
23527
23640
|
const probe = await probeMcpAuth(
|
|
23528
23641
|
`${ctx.mcpBaseUrl}?project=${encodeURIComponent(projectSlug)}&branch=${encodeURIComponent(job.archme_branch)}`,
|
|
@@ -23533,15 +23646,15 @@ async function prefetchJobContext(ctx, job, deviceId, repoCredentials = getRepoC
|
|
|
23533
23646
|
`MCP auth preflight failed after ${probe.attempts} attempt(s) (last status ${probe.lastStatus ?? "n/a"}): ${probe.detail}. Not starting the sandbox \u2014 failing the job so it retries with a freshly minted token.`
|
|
23534
23647
|
);
|
|
23535
23648
|
}
|
|
23536
|
-
const marker = job.source_mode === "pr_update" || firstScan ? { sha: null, repo: null } : await
|
|
23649
|
+
const marker = job.source_mode === "pr_update" || firstScan ? { sha: null, repo: null } : await reads.marker(`${job.repo_owner}/${job.repo_name}`);
|
|
23537
23650
|
const jobRepo = `${job.repo_owner}/${job.repo_name}`;
|
|
23538
|
-
const mappings = await
|
|
23651
|
+
const mappings = await reads.repoMappings(log);
|
|
23539
23652
|
let jobScope = jobRepoScope(mappings, jobRepo);
|
|
23540
23653
|
if (!jobScope) log("repo mappings unreadable: proceeding unseeded");
|
|
23541
23654
|
let seedArch = null;
|
|
23542
23655
|
if (mappings && jobScope && !firstScan) {
|
|
23543
23656
|
try {
|
|
23544
|
-
seedArch = await
|
|
23657
|
+
seedArch = await reads.seedArchitecture();
|
|
23545
23658
|
if (job.source_mode === "pr_update") {
|
|
23546
23659
|
jobScope = forkRepoScope(mappings, jobRepo, new Set(seedArch.components.map((c3) => c3.path)));
|
|
23547
23660
|
}
|
|
@@ -23549,7 +23662,7 @@ async function prefetchJobContext(ctx, job, deviceId, repoCredentials = getRepoC
|
|
|
23549
23662
|
log(`seed architecture unavailable (the agent runs unseeded): ${e.message}`);
|
|
23550
23663
|
}
|
|
23551
23664
|
}
|
|
23552
|
-
const bugsFolderId = await
|
|
23665
|
+
const bugsFolderId = await reads.bugsFolderId();
|
|
23553
23666
|
const model = await resolveRunnableModel(ctx.supabase, { warn: log });
|
|
23554
23667
|
const credentials = await repoCredentials(ctx, job.id, deviceId);
|
|
23555
23668
|
return {
|
|
@@ -23603,8 +23716,9 @@ async function completeClaudeRun(ctx, job, deviceId, run) {
|
|
|
23603
23716
|
metrics.model_served = runResult.modelServed;
|
|
23604
23717
|
metrics.usage = runResult.usage;
|
|
23605
23718
|
metrics.cost_usd = runResult.costUsd;
|
|
23606
|
-
const
|
|
23607
|
-
const {
|
|
23719
|
+
const jobReads = run.reads ?? readsFor(ctx, job, deviceId);
|
|
23720
|
+
const { total: writes, diffWrites, structuralWrites } = await jobReads.writesByPhase();
|
|
23721
|
+
const { reads, noChange } = await jobReads.mcpActivity();
|
|
23608
23722
|
markPhase("write_detect", {
|
|
23609
23723
|
commits: writes,
|
|
23610
23724
|
diff_writes: diffWrites,
|
|
@@ -23873,8 +23987,9 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
23873
23987
|
verdict
|
|
23874
23988
|
);
|
|
23875
23989
|
}
|
|
23990
|
+
const jobReads = readsFor(ctx, job, deviceId);
|
|
23876
23991
|
if (firstScan) {
|
|
23877
|
-
const gate = firstScanGate(await
|
|
23992
|
+
const gate = firstScanGate(await jobReads.architectureCounts());
|
|
23878
23993
|
if (!gate.ok) throw new Error(gate.reason);
|
|
23879
23994
|
}
|
|
23880
23995
|
await completeClaudeRun(ctx, job, deviceId, {
|
|
@@ -23885,7 +24000,8 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
23885
24000
|
affected: outcome.affected,
|
|
23886
24001
|
startedAt,
|
|
23887
24002
|
markPhase,
|
|
23888
|
-
log
|
|
24003
|
+
log,
|
|
24004
|
+
reads: jobReads
|
|
23889
24005
|
});
|
|
23890
24006
|
return;
|
|
23891
24007
|
}
|
|
@@ -24766,10 +24882,11 @@ var init_run = __esm({
|
|
|
24766
24882
|
init_localPrJob();
|
|
24767
24883
|
init_localPoller();
|
|
24768
24884
|
init_setting();
|
|
24769
|
-
|
|
24885
|
+
init_architectureCounts2();
|
|
24770
24886
|
init_imageManifest();
|
|
24771
24887
|
init_claimStance();
|
|
24772
24888
|
init_platformDevices();
|
|
24889
|
+
init_jobReads();
|
|
24773
24890
|
init_claudeVersion();
|
|
24774
24891
|
init_host();
|
|
24775
24892
|
init_runSandboxedJob();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archme/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Map your codebase into a living architecture diagram: run `archme init` in a repo to scan it, `archme daemon start` to keep it current on every commit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
package/sandbox-image.json
CHANGED