archctx 0.4.3 → 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.3",
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:902f7d7738d4b4d34861260a09a9a0e005004152c48228c91f6861043f2ba74f"
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/v3", 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.3", 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) {
@@ -8045,23 +8045,25 @@ function renderArchitectureDocumentationProjection(input) {
8045
8045
  const targetDrafts = architectureDocumentationTargetDrafts(model, layout2);
8046
8046
  const declaresSourceByNodeId = new Map(model.nodes.map((node) => [node.id, (nativeNodeSource(node)?.include ?? []).length > 0]));
8047
8047
  const changeSinceStampByNodeId = new Map(input.sourceChangesSinceStamp.map((entry) => [entry.nodeId, entry]));
8048
+ const priorStampByNodeId = previousProjectionManifestStamps(existingByPath.get("docs/architecture/.projection-manifest.json"));
8048
8049
  const notices = [];
8049
8050
  const rendered = targetDrafts.map((draft) => {
8050
8051
  const existing = existingByPath.get(draft.path);
8051
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
+ });
8052
8061
  let targetSourceDigest = input.sourceDigest;
8053
8062
  let targetVerifiedAgainst;
8054
8063
  if (nodeId !== undefined) {
8055
- targetSourceDigest = digestJson(renderTargetGeneratedBody(draft, model, {
8056
- generatedAt,
8057
- verifiedAgainst: CANONICAL_STICKY_STAMP,
8058
- scaleSignalsByNodeId,
8059
- semanticCompilationsByNodeId,
8060
- decisions: input.decisions ?? [],
8061
- timeline: input.timeline ?? [],
8062
- layout: layout2
8063
- }));
8064
+ targetSourceDigest = digestJson(generatedBody);
8064
8065
  const decision = stickyVerifiedAgainst(existing, draft.targetId, targetSourceDigest, {
8066
+ priorStamp: priorStampByNodeId.get(nodeId),
8065
8067
  nodeDeclaresSource: declaresSourceByNodeId.get(nodeId) === true,
8066
8068
  measurement: changeSinceStampByNodeId.get(nodeId)
8067
8069
  });
@@ -8077,22 +8079,12 @@ function renderArchitectureDocumentationProjection(input) {
8077
8079
  }
8078
8080
  targetVerifiedAgainst = decision.kind === "reuse" ? decision.verifiedAgainst : verifiedAgainst;
8079
8081
  }
8080
- const generatedBody = renderTargetGeneratedBody(draft, model, {
8081
- generatedAt,
8082
- verifiedAgainst: targetVerifiedAgainst ?? verifiedAgainst,
8083
- scaleSignalsByNodeId,
8084
- semanticCompilationsByNodeId,
8085
- decisions: input.decisions ?? [],
8086
- timeline: input.timeline ?? [],
8087
- layout: layout2
8088
- });
8089
8082
  const generatedBodyDigest = digestJson({ targetId: draft.targetId, body: generatedBody });
8090
8083
  const target = projectionTarget({
8091
8084
  ...draft,
8092
8085
  rendererVersion,
8093
8086
  sourceDigest: targetSourceDigest,
8094
- outputDigest: generatedBodyDigest,
8095
- ...targetVerifiedAgainst ? { verifiedAgainst: targetVerifiedAgainst } : {}
8087
+ outputDigest: generatedBodyDigest
8096
8088
  });
8097
8089
  const wrapped = wrapGeneratedRegion(target, generatedBody);
8098
8090
  const body = mergeGeneratedRegion(target, wrapped, existing, draft.skeleton);
@@ -8214,6 +8206,39 @@ function renderArchitectureDocumentationProjection(input) {
8214
8206
  notices
8215
8207
  };
8216
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
+ }
8217
8242
  function architectureProjectionSemanticBaseline(body) {
8218
8243
  if (body === undefined)
8219
8244
  return;
@@ -8358,6 +8383,30 @@ function countFileLines(absolute) {
8358
8383
  return content.endsWith(`
8359
8384
  `) ? newlines : newlines + 1;
8360
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
+ }
8361
8410
  function architectureDocumentationSourceDigest(input) {
8362
8411
  return digestJson({
8363
8412
  model: input.model,
@@ -8834,7 +8883,7 @@ function projectionTarget(input) {
8834
8883
  path: input.path,
8835
8884
  ownership: input.ownership,
8836
8885
  generatedRegion: {
8837
- startMarker: generatedStartMarker(input.targetId, input.sourceDigest, input.rendererVersion, input.outputDigest, input.verifiedAgainst),
8886
+ startMarker: generatedStartMarker(input.targetId, input.sourceDigest, input.rendererVersion, input.outputDigest),
8838
8887
  endMarker: generatedEndMarker(input.targetId)
8839
8888
  },
8840
8889
  rendererVersion: input.rendererVersion,
@@ -8849,7 +8898,6 @@ function renderTargetGeneratedBody(target, model, input) {
8849
8898
  if (target.type === "entity-summary") {
8850
8899
  const node = model.nodes.find((entry) => entry.id === target.scope.id);
8851
8900
  return renderEntitySummary(node, model, {
8852
- verifiedAgainst: input.verifiedAgainst,
8853
8901
  scaleSignal: input.scaleSignalsByNodeId.get(node.id),
8854
8902
  semanticCompilation: input.semanticCompilationsByNodeId.get(node.id)
8855
8903
  });
@@ -8913,11 +8961,10 @@ function renderEntitySummary(node, model, input) {
8913
8961
  const localContracts = contractFilesForNode(node);
8914
8962
  const lines = [
8915
8963
  `> **狀態**:${node.status ? `\`${node.status}\`` : "未宣告(`status` 缺失)"}`,
8916
- `> **Verified against**:\`${input.verifiedAgainst.branch}@${input.verifiedAgainst.commit}\`(${input.verifiedAgainst.committedAt.slice(0, 10)})`,
8917
8964
  `> **Capability ID**:\`${node.id}\`(kind \`${node.kind}\`)`,
8918
8965
  `> **Matched Prefixes**:${include.length > 0 ? include.map((pattern) => `\`${pattern}\``).join("、") : "未宣告(`source.include` 缺失)"}`,
8919
8966
  `> **Local Contracts**:${localContracts.length > 0 ? localContracts.map((entry) => `\`${entry}\``).join("、") : "未宣告(`extensions.localContracts` 缺失)"}`,
8920
- "> **事實優先級**:倉庫當前狀態 > 本文檔機器區 > 本文檔人工區。機器區(引言、§1、§2)由 ArchContext 從架構模型與 Git 狀態投影生成,手改會在下次投影被覆蓋。",
8967
+ "> **事實優先級**:倉庫當前狀態 > 本文檔機器區 > 本文檔人工區。機器區(引言、§1、§2)由 ArchContext 從架構模型與源碼度量投影生成,手改會在下次投影被覆蓋。本文檔不記錄出處;本次投影所驗證的 commit 見 `docs/architecture/.projection-manifest.json`。",
8921
8968
  ...node.summary ? ["", node.summary] : [],
8922
8969
  "",
8923
8970
  "## 1. P1:能力架構地圖",
@@ -8937,11 +8984,10 @@ function renderEntitySummary(node, model, input) {
8937
8984
  "### 1.3 規模信號",
8938
8985
  "",
8939
8986
  ...input.scaleSignal ? [
8940
- `- 文件數:\`${input.scaleSignal.fileCount}\``,
8941
- `- 總行數:\`${input.scaleSignal.lineCount}\``,
8987
+ `- 規模量級:\`${scaleMagnitudeBucketLabel(input.scaleSignal.fileCount)}\` 個文件 / \`${scaleMagnitudeBucketLabel(input.scaleSignal.lineCount)}\` 行`,
8942
8988
  `- 匹配前綴:${input.scaleSignal.includePatterns.map((pattern) => `\`${pattern}\``).join("、")}`,
8943
8989
  ...input.scaleSignal.excludePatterns.length > 0 ? [`- 排除前綴:${input.scaleSignal.excludePatterns.map((pattern) => `\`${pattern}\``).join("、")}`] : [],
8944
- "- 復算:`archctx docs plan --json`(掃描 `source.include` 減 `source.exclude`,跳過 `.git/` 與 `node_modules/`)"
8990
+ "- 推導:掃描 `source.include` 減 `source.exclude`,跳過 `.git/` 與 `node_modules/`,再按 1–2–5 階梯分桶。精確計數不入本文檔:量級足以回答「這個能力有多大」,而逐行計數會讓覆蓋範圍內任何一次源碼改動都改寫本文檔。"
8945
8991
  ] : ["- 未宣告 `source.include`,規模信號無法推導。"],
8946
8992
  "",
8947
8993
  "### 1.4 依賴邊界",
@@ -9202,39 +9248,12 @@ function parseGeneratedRegionMetadata(marker) {
9202
9248
  return {
9203
9249
  sourceDigest: marker.match(/sourceDigest="([^"]+)"/)?.[1],
9204
9250
  rendererVersion: marker.match(/rendererVersion="([^"]+)"/)?.[1],
9205
- outputDigest: marker.match(/outputDigest="([^"]+)"/)?.[1],
9206
- verifiedAgainst: marker.match(/verifiedAgainst="([^"]+)"/)?.[1]
9251
+ outputDigest: marker.match(/outputDigest="([^"]+)"/)?.[1]
9207
9252
  };
9208
9253
  }
9209
- function generatedStartMarker(targetId, sourceDigest, rendererVersion, outputDigest, verifiedAgainst) {
9210
- const stamp = verifiedAgainst ? serializeMarkerVerifiedAgainst(verifiedAgainst) : undefined;
9211
- return `${ARCHITECTURE_DOCS_GENERATED_BEGIN_PREFIX} target="${targetId}" sourceDigest="${sourceDigest}" rendererVersion="${rendererVersion}" outputDigest="${outputDigest}"${stamp ? ` verifiedAgainst="${stamp}"` : ""} -->`;
9254
+ function generatedStartMarker(targetId, sourceDigest, rendererVersion, outputDigest) {
9255
+ return `${ARCHITECTURE_DOCS_GENERATED_BEGIN_PREFIX} target="${targetId}" sourceDigest="${sourceDigest}" rendererVersion="${rendererVersion}" outputDigest="${outputDigest}" -->`;
9212
9256
  }
9213
- function serializeMarkerVerifiedAgainst(value) {
9214
- const stamp = `${value.branch}@${value.commit}@${value.committedAt}`;
9215
- return /["<>]/.test(stamp) ? undefined : stamp;
9216
- }
9217
- function parseMarkerVerifiedAgainst(value) {
9218
- if (!value)
9219
- return;
9220
- const parts = value.split("@");
9221
- if (parts.length < 3)
9222
- return;
9223
- try {
9224
- return assertArchitectureProjectionVerifiedAgainst({
9225
- branch: parts.slice(0, -2).join("@"),
9226
- commit: parts[parts.length - 2],
9227
- committedAt: parts[parts.length - 1]
9228
- });
9229
- } catch {
9230
- return;
9231
- }
9232
- }
9233
- var CANONICAL_STICKY_STAMP = {
9234
- branch: "__sticky-canonical__",
9235
- commit: "0000000",
9236
- committedAt: "1970-01-01T00:00:00.000Z"
9237
- };
9238
9257
  function stickyVerifiedAgainst(existing, targetId, nodeSourceDigest, node) {
9239
9258
  if (!existing)
9240
9259
  return { kind: "restamp" };
@@ -9244,7 +9263,7 @@ function stickyVerifiedAgainst(existing, targetId, nodeSourceDigest, node) {
9244
9263
  const metadata = parseGeneratedRegionMetadata(region.startMarker);
9245
9264
  if (metadata.sourceDigest !== nodeSourceDigest)
9246
9265
  return { kind: "restamp" };
9247
- const stamp = parseMarkerVerifiedAgainst(metadata.verifiedAgainst);
9266
+ const stamp = node.priorStamp;
9248
9267
  if (!stamp)
9249
9268
  return { kind: "restamp" };
9250
9269
  if (!node.nodeDeclaresSource)
@@ -29894,8 +29913,8 @@ class ArchctxDaemon {
29894
29913
  ownerRegistry: loadPracticeWaiverOwnerRegistry(session.workspace.root),
29895
29914
  now: this.clock()
29896
29915
  }) : undefined;
29897
- const projectionDrift = this.completeTaskProjectionDrift(session.workspace.root);
29898
- const projectionFreshness = this.completeTaskProjectionFreshness(session.workspace.root);
29916
+ const projectionDrift = completeTaskProjectionDrift(session.workspace.root);
29917
+ const projectionFreshness = completeTaskProjectionFreshness(session.workspace.root);
29899
29918
  const reviewInput = {
29900
29919
  taskSessionId,
29901
29920
  posture: input.posture ?? "normal",
@@ -29916,12 +29935,6 @@ class ArchctxDaemon {
29916
29935
  await this.localStore.saveReviewResult(review.reviewId, review);
29917
29936
  return okEnvelope("complete_task", review);
29918
29937
  }
29919
- completeTaskProjectionDrift(root) {
29920
- return completeTaskProjectionDrift(root);
29921
- }
29922
- completeTaskProjectionFreshness(root) {
29923
- return completeTaskProjectionFreshness(root);
29924
- }
29925
29938
  manualExternalDocumentation() {
29926
29939
  if (this.externalDocumentationInjected)
29927
29940
  return this.externalDocumentation;
@@ -35494,8 +35507,8 @@ class ArchctxDaemon2 {
35494
35507
  ownerRegistry: loadPracticeWaiverOwnerRegistry(session.workspace.root),
35495
35508
  now: this.clock()
35496
35509
  }) : undefined;
35497
- const projectionDrift = this.completeTaskProjectionDrift(session.workspace.root);
35498
- const projectionFreshness = this.completeTaskProjectionFreshness(session.workspace.root);
35510
+ const projectionDrift = completeTaskProjectionDrift2(session.workspace.root);
35511
+ const projectionFreshness = completeTaskProjectionFreshness2(session.workspace.root);
35499
35512
  const reviewInput = {
35500
35513
  taskSessionId,
35501
35514
  posture: input.posture ?? "normal",
@@ -35516,12 +35529,6 @@ class ArchctxDaemon2 {
35516
35529
  await this.localStore.saveReviewResult(review.reviewId, review);
35517
35530
  return okEnvelope("complete_task", review);
35518
35531
  }
35519
- completeTaskProjectionDrift(root) {
35520
- return completeTaskProjectionDrift2(root);
35521
- }
35522
- completeTaskProjectionFreshness(root) {
35523
- return completeTaskProjectionFreshness2(root);
35524
- }
35525
35532
  manualExternalDocumentation() {
35526
35533
  if (this.externalDocumentationInjected)
35527
35534
  return this.externalDocumentation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archctx",
3
- "version": "0.4.3",
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/v3" },
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/v3" },
45
+ "rendererVersion": { "const": "archcontext.docs-renderer/v4" },
46
46
  "layoutVersion": { "const": "archcontext.docs-layout/v1" },
47
47
  "generatedFrom": {
48
48
  "type": "object",