archctx 0.4.2 → 0.4.4

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "archcontext.practice-catalog-manifest/v1",
3
3
  "catalogVersion": "2026.06.0",
4
- "productVersion": "0.4.2",
4
+ "productVersion": "0.4.4",
5
5
  "generatedAt": "1970-01-01T00:00:00.000Z",
6
6
  "entries": [
7
7
  {
@@ -389,5 +389,5 @@
389
389
  "structurizr.dsl",
390
390
  "twelve-factor"
391
391
  ],
392
- "catalogDigest": "sha256:7fc97d6c05657eeeee604a302a22e0afe77dc2d0ffea89442de193e570d9b8bf"
392
+ "catalogDigest": "sha256:02457d42fb45b16f949b88355ec2ba83c53e8d7e74d32fba28500984d89e1b64"
393
393
  }
package/bin/archctx.mjs CHANGED
@@ -919,7 +919,7 @@ function sortedUniqueIssues(label, values) {
919
919
  const expected = [...new Set(values)].sort();
920
920
  return expected.length === values.length && expected.every((value, index) => value === values[index]) ? [] : [`${label} must be sorted and unique`];
921
921
  }
922
- var PROJECTION_REQUEST_SCHEMA_VERSION = "archcontext.projection-request/v1", PROJECTION_RESULT_SCHEMA_VERSION = "archcontext.projection-result/v1", ARCHITECTURE_REFRESH_SIGNAL_SCHEMA_VERSION = "archcontext.architecture-refresh-signal/v1", ARCHCTX_CAPABILITIES_SCHEMA_VERSION = "archcontext.capabilities/v1", ARCHITECTURE_DOCS_RENDERER_VERSION = "archcontext.docs-renderer/v2", AGENT_CONTEXT_RENDERER_VERSION = "archcontext.agent-context-renderer/v1", PROJECTION_MODES, PROJECTION_TARGETS, ARCHITECTURE_MAJOR_CHANGE_REASON_CODES, ARCHCTX_FEATURES;
922
+ var PROJECTION_REQUEST_SCHEMA_VERSION = "archcontext.projection-request/v1", PROJECTION_RESULT_SCHEMA_VERSION = "archcontext.projection-result/v1", ARCHITECTURE_REFRESH_SIGNAL_SCHEMA_VERSION = "archcontext.architecture-refresh-signal/v1", ARCHCTX_CAPABILITIES_SCHEMA_VERSION = "archcontext.capabilities/v1", ARCHITECTURE_DOCS_RENDERER_VERSION = "archcontext.docs-renderer/v4", AGENT_CONTEXT_RENDERER_VERSION = "archcontext.agent-context-renderer/v1", PROJECTION_MODES, PROJECTION_TARGETS, ARCHITECTURE_MAJOR_CHANGE_REASON_CODES, ARCHCTX_FEATURES;
923
923
  var init_projection = __esm(() => {
924
924
  init_schema();
925
925
  PROJECTION_MODES = ["check", "plan", "apply", "adopt"];
@@ -1000,7 +1000,7 @@ function productVersionManifest() {
1000
1000
  }
1001
1001
  };
1002
1002
  }
1003
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.4.2", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10", ARCHCONTEXT_NODE_RANGE = ">=24 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
1003
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.4.4", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10", ARCHCONTEXT_NODE_RANGE = ">=24 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
1004
1004
 
1005
1005
  // packages/contracts/src/validator.ts
1006
1006
  function validateJsonSchema(schema, value) {
@@ -8042,21 +8042,28 @@ function renderArchitectureDocumentationProjection(input) {
8042
8042
  resulting: semanticState,
8043
8043
  acceptedChange: input.refreshContext?.acceptedChange
8044
8044
  });
8045
- const entitySourceDigest = digestJson({
8046
- sourceDigest: input.sourceDigest,
8047
- sourceScaleSignals: [...scaleSignalsByNodeId.values()].sort((left, right) => left.nodeId.localeCompare(right.nodeId)),
8048
- semanticProofs: [...semanticCompilationsByNodeId.values()].map((compilation) => ({ capabilityId: compilation.capabilityId, proofDigest: compilation.proofDigest })).sort((left, right) => left.capabilityId.localeCompare(right.capabilityId))
8049
- });
8050
8045
  const targetDrafts = architectureDocumentationTargetDrafts(model, layout2);
8051
8046
  const declaresSourceByNodeId = new Map(model.nodes.map((node) => [node.id, (nativeNodeSource(node)?.include ?? []).length > 0]));
8052
8047
  const changeSinceStampByNodeId = new Map(input.sourceChangesSinceStamp.map((entry) => [entry.nodeId, entry]));
8048
+ const priorStampByNodeId = previousProjectionManifestStamps(existingByPath.get("docs/architecture/.projection-manifest.json"));
8053
8049
  const notices = [];
8054
8050
  const rendered = targetDrafts.map((draft) => {
8055
8051
  const existing = existingByPath.get(draft.path);
8056
8052
  const nodeId = draft.type === "entity-summary" ? draft.scope.id : undefined;
8053
+ const generatedBody = renderTargetGeneratedBody(draft, model, {
8054
+ generatedAt,
8055
+ scaleSignalsByNodeId,
8056
+ semanticCompilationsByNodeId,
8057
+ decisions: input.decisions ?? [],
8058
+ timeline: input.timeline ?? [],
8059
+ layout: layout2
8060
+ });
8061
+ let targetSourceDigest = input.sourceDigest;
8057
8062
  let targetVerifiedAgainst;
8058
8063
  if (nodeId !== undefined) {
8059
- const decision = stickyVerifiedAgainst(existing, draft.targetId, entitySourceDigest, {
8064
+ targetSourceDigest = digestJson(generatedBody);
8065
+ const decision = stickyVerifiedAgainst(existing, draft.targetId, targetSourceDigest, {
8066
+ priorStamp: priorStampByNodeId.get(nodeId),
8060
8067
  nodeDeclaresSource: declaresSourceByNodeId.get(nodeId) === true,
8061
8068
  measurement: changeSinceStampByNodeId.get(nodeId)
8062
8069
  });
@@ -8072,22 +8079,12 @@ function renderArchitectureDocumentationProjection(input) {
8072
8079
  }
8073
8080
  targetVerifiedAgainst = decision.kind === "reuse" ? decision.verifiedAgainst : verifiedAgainst;
8074
8081
  }
8075
- const generatedBody = renderTargetGeneratedBody(draft, model, {
8076
- generatedAt,
8077
- verifiedAgainst: targetVerifiedAgainst ?? verifiedAgainst,
8078
- scaleSignalsByNodeId,
8079
- semanticCompilationsByNodeId,
8080
- decisions: input.decisions ?? [],
8081
- timeline: input.timeline ?? [],
8082
- layout: layout2
8083
- });
8084
8082
  const generatedBodyDigest = digestJson({ targetId: draft.targetId, body: generatedBody });
8085
8083
  const target = projectionTarget({
8086
8084
  ...draft,
8087
8085
  rendererVersion,
8088
- sourceDigest: draft.type === "entity-summary" ? entitySourceDigest : input.sourceDigest,
8089
- outputDigest: generatedBodyDigest,
8090
- ...targetVerifiedAgainst ? { verifiedAgainst: targetVerifiedAgainst } : {}
8086
+ sourceDigest: targetSourceDigest,
8087
+ outputDigest: generatedBodyDigest
8091
8088
  });
8092
8089
  const wrapped = wrapGeneratedRegion(target, generatedBody);
8093
8090
  const body = mergeGeneratedRegion(target, wrapped, existing, draft.skeleton);
@@ -8209,6 +8206,39 @@ function renderArchitectureDocumentationProjection(input) {
8209
8206
  notices
8210
8207
  };
8211
8208
  }
8209
+ function previousProjectionManifestStamps(body) {
8210
+ const stamps = new Map;
8211
+ if (body === undefined)
8212
+ return stamps;
8213
+ let parsed;
8214
+ try {
8215
+ parsed = JSON.parse(body);
8216
+ } catch {
8217
+ return stamps;
8218
+ }
8219
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
8220
+ return stamps;
8221
+ const targets = parsed.targets;
8222
+ if (!Array.isArray(targets))
8223
+ return stamps;
8224
+ for (const entry of targets) {
8225
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
8226
+ continue;
8227
+ const record2 = entry;
8228
+ if (record2.type !== "entity-summary")
8229
+ continue;
8230
+ const scope = record2.scope;
8231
+ const nodeId = scope && typeof scope === "object" && !Array.isArray(scope) ? scope.id : undefined;
8232
+ if (typeof nodeId !== "string" || nodeId === "")
8233
+ continue;
8234
+ try {
8235
+ stamps.set(nodeId, assertArchitectureProjectionVerifiedAgainst(record2.verifiedAgainst));
8236
+ } catch {
8237
+ continue;
8238
+ }
8239
+ }
8240
+ return stamps;
8241
+ }
8212
8242
  function architectureProjectionSemanticBaseline(body) {
8213
8243
  if (body === undefined)
8214
8244
  return;
@@ -8353,6 +8383,30 @@ function countFileLines(absolute) {
8353
8383
  return content.endsWith(`
8354
8384
  `) ? newlines : newlines + 1;
8355
8385
  }
8386
+ function scaleMagnitudeBucketLabel(value) {
8387
+ if (!Number.isSafeInteger(value) || value < 0) {
8388
+ throw new Error(`architecture-docs-projection-scale-signal-not-a-count: ${value}`);
8389
+ }
8390
+ if (value === 0)
8391
+ return "0";
8392
+ const mantissas = [1, 2, 5];
8393
+ let index = 0;
8394
+ let magnitude = 1;
8395
+ let lower = 1;
8396
+ for (;; ) {
8397
+ const nextIndex = (index + 1) % mantissas.length;
8398
+ const nextMagnitude = nextIndex === 0 ? magnitude * 10 : magnitude;
8399
+ const upper = mantissas[nextIndex] * nextMagnitude;
8400
+ if (upper > value) {
8401
+ const unit = lower >= 1e6 ? 1e6 : lower >= 1e4 ? 1000 : 1;
8402
+ const suffix = unit === 1e6 ? "M" : unit === 1000 ? "k" : "";
8403
+ return `${lower / unit}${suffix}–${upper / unit}${suffix}`;
8404
+ }
8405
+ index = nextIndex;
8406
+ magnitude = nextMagnitude;
8407
+ lower = upper;
8408
+ }
8409
+ }
8356
8410
  function architectureDocumentationSourceDigest(input) {
8357
8411
  return digestJson({
8358
8412
  model: input.model,
@@ -8829,7 +8883,7 @@ function projectionTarget(input) {
8829
8883
  path: input.path,
8830
8884
  ownership: input.ownership,
8831
8885
  generatedRegion: {
8832
- startMarker: generatedStartMarker(input.targetId, input.sourceDigest, input.rendererVersion, input.outputDigest, input.verifiedAgainst),
8886
+ startMarker: generatedStartMarker(input.targetId, input.sourceDigest, input.rendererVersion, input.outputDigest),
8833
8887
  endMarker: generatedEndMarker(input.targetId)
8834
8888
  },
8835
8889
  rendererVersion: input.rendererVersion,
@@ -8844,7 +8898,6 @@ function renderTargetGeneratedBody(target, model, input) {
8844
8898
  if (target.type === "entity-summary") {
8845
8899
  const node = model.nodes.find((entry) => entry.id === target.scope.id);
8846
8900
  return renderEntitySummary(node, model, {
8847
- verifiedAgainst: input.verifiedAgainst,
8848
8901
  scaleSignal: input.scaleSignalsByNodeId.get(node.id),
8849
8902
  semanticCompilation: input.semanticCompilationsByNodeId.get(node.id)
8850
8903
  });
@@ -8908,11 +8961,10 @@ function renderEntitySummary(node, model, input) {
8908
8961
  const localContracts = contractFilesForNode(node);
8909
8962
  const lines = [
8910
8963
  `> **狀態**:${node.status ? `\`${node.status}\`` : "未宣告(`status` 缺失)"}`,
8911
- `> **Verified against**:\`${input.verifiedAgainst.branch}@${input.verifiedAgainst.commit}\`(${input.verifiedAgainst.committedAt.slice(0, 10)})`,
8912
8964
  `> **Capability ID**:\`${node.id}\`(kind \`${node.kind}\`)`,
8913
8965
  `> **Matched Prefixes**:${include.length > 0 ? include.map((pattern) => `\`${pattern}\``).join("、") : "未宣告(`source.include` 缺失)"}`,
8914
8966
  `> **Local Contracts**:${localContracts.length > 0 ? localContracts.map((entry) => `\`${entry}\``).join("、") : "未宣告(`extensions.localContracts` 缺失)"}`,
8915
- "> **事實優先級**:倉庫當前狀態 > 本文檔機器區 > 本文檔人工區。機器區(引言、§1、§2)由 ArchContext 從架構模型與 Git 狀態投影生成,手改會在下次投影被覆蓋。",
8967
+ "> **事實優先級**:倉庫當前狀態 > 本文檔機器區 > 本文檔人工區。機器區(引言、§1、§2)由 ArchContext 從架構模型與源碼度量投影生成,手改會在下次投影被覆蓋。本文檔不記錄出處;本次投影所驗證的 commit 見 `docs/architecture/.projection-manifest.json`。",
8916
8968
  ...node.summary ? ["", node.summary] : [],
8917
8969
  "",
8918
8970
  "## 1. P1:能力架構地圖",
@@ -8932,11 +8984,10 @@ function renderEntitySummary(node, model, input) {
8932
8984
  "### 1.3 規模信號",
8933
8985
  "",
8934
8986
  ...input.scaleSignal ? [
8935
- `- 文件數:\`${input.scaleSignal.fileCount}\``,
8936
- `- 總行數:\`${input.scaleSignal.lineCount}\``,
8987
+ `- 規模量級:\`${scaleMagnitudeBucketLabel(input.scaleSignal.fileCount)}\` 個文件 / \`${scaleMagnitudeBucketLabel(input.scaleSignal.lineCount)}\` 行`,
8937
8988
  `- 匹配前綴:${input.scaleSignal.includePatterns.map((pattern) => `\`${pattern}\``).join("、")}`,
8938
8989
  ...input.scaleSignal.excludePatterns.length > 0 ? [`- 排除前綴:${input.scaleSignal.excludePatterns.map((pattern) => `\`${pattern}\``).join("、")}`] : [],
8939
- "- 復算:`archctx docs plan --json`(掃描 `source.include` 減 `source.exclude`,跳過 `.git/` 與 `node_modules/`)"
8990
+ "- 推導:掃描 `source.include` 減 `source.exclude`,跳過 `.git/` 與 `node_modules/`,再按 1–2–5 階梯分桶。精確計數不入本文檔:量級足以回答「這個能力有多大」,而逐行計數會讓覆蓋範圍內任何一次源碼改動都改寫本文檔。"
8940
8991
  ] : ["- 未宣告 `source.include`,規模信號無法推導。"],
8941
8992
  "",
8942
8993
  "### 1.4 依賴邊界",
@@ -9197,44 +9248,22 @@ function parseGeneratedRegionMetadata(marker) {
9197
9248
  return {
9198
9249
  sourceDigest: marker.match(/sourceDigest="([^"]+)"/)?.[1],
9199
9250
  rendererVersion: marker.match(/rendererVersion="([^"]+)"/)?.[1],
9200
- outputDigest: marker.match(/outputDigest="([^"]+)"/)?.[1],
9201
- verifiedAgainst: marker.match(/verifiedAgainst="([^"]+)"/)?.[1]
9251
+ outputDigest: marker.match(/outputDigest="([^"]+)"/)?.[1]
9202
9252
  };
9203
9253
  }
9204
- function generatedStartMarker(targetId, sourceDigest, rendererVersion, outputDigest, verifiedAgainst) {
9205
- const stamp = verifiedAgainst ? serializeMarkerVerifiedAgainst(verifiedAgainst) : undefined;
9206
- return `${ARCHITECTURE_DOCS_GENERATED_BEGIN_PREFIX} target="${targetId}" sourceDigest="${sourceDigest}" rendererVersion="${rendererVersion}" outputDigest="${outputDigest}"${stamp ? ` verifiedAgainst="${stamp}"` : ""} -->`;
9207
- }
9208
- function serializeMarkerVerifiedAgainst(value) {
9209
- const stamp = `${value.branch}@${value.commit}@${value.committedAt}`;
9210
- return /["<>]/.test(stamp) ? undefined : stamp;
9254
+ function generatedStartMarker(targetId, sourceDigest, rendererVersion, outputDigest) {
9255
+ return `${ARCHITECTURE_DOCS_GENERATED_BEGIN_PREFIX} target="${targetId}" sourceDigest="${sourceDigest}" rendererVersion="${rendererVersion}" outputDigest="${outputDigest}" -->`;
9211
9256
  }
9212
- function parseMarkerVerifiedAgainst(value) {
9213
- if (!value)
9214
- return;
9215
- const parts = value.split("@");
9216
- if (parts.length < 3)
9217
- return;
9218
- try {
9219
- return assertArchitectureProjectionVerifiedAgainst({
9220
- branch: parts.slice(0, -2).join("@"),
9221
- commit: parts[parts.length - 2],
9222
- committedAt: parts[parts.length - 1]
9223
- });
9224
- } catch {
9225
- return;
9226
- }
9227
- }
9228
- function stickyVerifiedAgainst(existing, targetId, entitySourceDigest, node) {
9257
+ function stickyVerifiedAgainst(existing, targetId, nodeSourceDigest, node) {
9229
9258
  if (!existing)
9230
9259
  return { kind: "restamp" };
9231
9260
  const region = findGeneratedRegion(existing, targetId);
9232
9261
  if (!region)
9233
9262
  return { kind: "restamp" };
9234
9263
  const metadata = parseGeneratedRegionMetadata(region.startMarker);
9235
- if (metadata.sourceDigest !== entitySourceDigest)
9264
+ if (metadata.sourceDigest !== nodeSourceDigest)
9236
9265
  return { kind: "restamp" };
9237
- const stamp = parseMarkerVerifiedAgainst(metadata.verifiedAgainst);
9266
+ const stamp = node.priorStamp;
9238
9267
  if (!stamp)
9239
9268
  return { kind: "restamp" };
9240
9269
  if (!node.nodeDeclaresSource)
@@ -29884,8 +29913,8 @@ class ArchctxDaemon {
29884
29913
  ownerRegistry: loadPracticeWaiverOwnerRegistry(session.workspace.root),
29885
29914
  now: this.clock()
29886
29915
  }) : undefined;
29887
- const projectionDrift = this.completeTaskProjectionDrift(session.workspace.root);
29888
- const projectionFreshness = this.completeTaskProjectionFreshness(session.workspace.root);
29916
+ const projectionDrift = completeTaskProjectionDrift(session.workspace.root);
29917
+ const projectionFreshness = completeTaskProjectionFreshness(session.workspace.root);
29889
29918
  const reviewInput = {
29890
29919
  taskSessionId,
29891
29920
  posture: input.posture ?? "normal",
@@ -29906,12 +29935,6 @@ class ArchctxDaemon {
29906
29935
  await this.localStore.saveReviewResult(review.reviewId, review);
29907
29936
  return okEnvelope("complete_task", review);
29908
29937
  }
29909
- completeTaskProjectionDrift(root) {
29910
- return completeTaskProjectionDrift(root);
29911
- }
29912
- completeTaskProjectionFreshness(root) {
29913
- return completeTaskProjectionFreshness(root);
29914
- }
29915
29938
  manualExternalDocumentation() {
29916
29939
  if (this.externalDocumentationInjected)
29917
29940
  return this.externalDocumentation;
@@ -35484,8 +35507,8 @@ class ArchctxDaemon2 {
35484
35507
  ownerRegistry: loadPracticeWaiverOwnerRegistry(session.workspace.root),
35485
35508
  now: this.clock()
35486
35509
  }) : undefined;
35487
- const projectionDrift = this.completeTaskProjectionDrift(session.workspace.root);
35488
- const projectionFreshness = this.completeTaskProjectionFreshness(session.workspace.root);
35510
+ const projectionDrift = completeTaskProjectionDrift2(session.workspace.root);
35511
+ const projectionFreshness = completeTaskProjectionFreshness2(session.workspace.root);
35489
35512
  const reviewInput = {
35490
35513
  taskSessionId,
35491
35514
  posture: input.posture ?? "normal",
@@ -35506,12 +35529,6 @@ class ArchctxDaemon2 {
35506
35529
  await this.localStore.saveReviewResult(review.reviewId, review);
35507
35530
  return okEnvelope("complete_task", review);
35508
35531
  }
35509
- completeTaskProjectionDrift(root) {
35510
- return completeTaskProjectionDrift2(root);
35511
- }
35512
- completeTaskProjectionFreshness(root) {
35513
- return completeTaskProjectionFreshness2(root);
35514
- }
35515
35532
  manualExternalDocumentation() {
35516
35533
  if (this.externalDocumentationInjected)
35517
35534
  return this.externalDocumentation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archctx",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Local architecture context CLI for agentic coding workflows.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -31,7 +31,7 @@
31
31
  "additionalProperties": false,
32
32
  "required": ["architectureDocs", "agentContext"],
33
33
  "properties": {
34
- "architectureDocs": { "const": "archcontext.docs-renderer/v2" },
34
+ "architectureDocs": { "const": "archcontext.docs-renderer/v4" },
35
35
  "agentContext": { "const": "archcontext.agent-context-renderer/v1" }
36
36
  }
37
37
  },
@@ -42,7 +42,7 @@
42
42
  "codeGraphDigest": { "$ref": "#/$defs/digest" },
43
43
  "indexedWorktreeDigest": { "$ref": "#/$defs/nullableDigest" },
44
44
  "projectionInputDigest": { "$ref": "#/$defs/digest" },
45
- "rendererVersion": { "const": "archcontext.docs-renderer/v2" },
45
+ "rendererVersion": { "const": "archcontext.docs-renderer/v4" },
46
46
  "layoutVersion": { "const": "archcontext.docs-layout/v1" },
47
47
  "generatedFrom": {
48
48
  "type": "object",