@jstn-sdk/ma 0.1.13 → 0.14.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/.agents/plugins/marketplace.json +0 -0
- package/.codex/agents/Architect.toml +1 -0
- package/.codex/agents/Auditor.toml +1 -0
- package/.codex/agents/Builder.toml +1 -0
- package/.codex/agents/Flow.toml +1 -0
- package/.codex/agents/Sage.toml +1 -0
- package/.codex/agents/Vibe.toml +1 -0
- package/.codex/hooks.json +12 -2
- package/.codex/prompts/enforcement.md +0 -0
- package/.codex/prompts/onboarding.md +6 -1
- package/.codex/prompts/release-rules.md +0 -0
- package/.codex/prompts/skill-contract.md +0 -0
- package/COVERAGE.md +7 -7
- package/DEMO.md +15 -6
- package/LICENSE +0 -0
- package/README.md +126 -793
- package/bin/ma.js +585 -97
- package/data/clone-data.ledger.json +0 -0
- package/data/clone-data.proof.json +0 -0
- package/data/clone-data.rvf +0 -0
- package/docs/README.md +8 -2
- package/docs/autonomous-tasks.md +46 -0
- package/docs/codex-integration.md +16 -0
- package/docs/getting-started.md +28 -4
- package/docs/installed-sdk.md +0 -0
- package/docs/mcp-setup.md +30 -1
- package/docs/qa/{release-issue-gates-0.1.13.json → release-issue-gates-0.14.0.json} +77 -77
- package/docs/qa/release-readiness-0.1.5.md +0 -0
- package/docs/qa/{release-readiness-0.1.13.md → release-readiness-0.14.0.md} +12 -12
- package/docs/quality.md +24 -0
- package/docs/reference/native-engineering-patterns.md +0 -0
- package/docs/reference/native-security-playbooks.md +0 -0
- package/docs/reference/native-source-selection.md +0 -0
- package/docs/reference/native-style-and-deslop.md +0 -0
- package/docs/release-spec.md +27 -14
- package/docs/skills.md +1 -1
- package/index.js +73 -0
- package/mcp/collections.json +0 -0
- package/mcp/fallback.json +0 -0
- package/mcp/local/code-intel.js +179 -41
- package/mcp/local/context.js +180 -0
- package/mcp/local/memory.js +17 -6
- package/mcp/local/playbooks.js +0 -0
- package/mcp/local/quality.js +39 -0
- package/mcp/local/state.js +7 -3
- package/mcp/local/team-run.js +8 -3
- package/mcp/local/trace.js +0 -0
- package/mcp/local-capabilities.json +9 -0
- package/mcp/native-playbooks.json +0 -0
- package/mcp/servers.json +0 -0
- package/package.json +26 -4
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +2 -2
- package/plugins/meta-architect/obsidian/main.js +137 -4
- package/plugins/meta-architect/obsidian/manifest.json +1 -1
- package/plugins/meta-architect/obsidian/styles.css +0 -0
- package/plugins/meta-architect/skills/align/SKILL.md +0 -0
- package/plugins/meta-architect/skills/align/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/align/references/shared-language.md +0 -0
- package/plugins/meta-architect/skills/arch/SKILL.md +0 -0
- package/plugins/meta-architect/skills/arch/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/build/SKILL.md +0 -0
- package/plugins/meta-architect/skills/build/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/cleanup/SKILL.md +0 -0
- package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +0 -0
- package/plugins/meta-architect/skills/diagnose/SKILL.md +0 -0
- package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +0 -0
- package/plugins/meta-architect/skills/flow/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/SKILL.md +0 -0
- package/plugins/meta-architect/skills/maestro/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +0 -0
- package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +0 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +0 -0
- package/plugins/meta-architect/skills/sage/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/sage/references/source-selection.md +0 -0
- package/plugins/meta-architect/skills/tdd/SKILL.md +0 -0
- package/plugins/meta-architect/skills/tdd/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +0 -0
- package/plugins/meta-architect/skills/vet/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +0 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +0 -0
- package/plugins/meta-architect/skills/vibe/agents/openai.yaml +0 -0
- package/schemas/autonomous-task-queue.schema.json +56 -0
- package/schemas/handoff-packet.schema.json +36 -0
- package/schemas/setup-receipt.schema.json +28 -0
- package/schemas/skill-frontmatter.schema.json +20 -0
- package/schemas/support-bundle.schema.json +37 -0
- package/schemas/task-contract.schema.json +31 -0
- package/scripts/active-autonomy-hook.mjs +141 -11
- package/scripts/biome-staged.js +0 -0
- package/scripts/build-linux-packages.mjs +7 -3
- package/scripts/cleanup-test-fixtures.sh +55 -0
- package/scripts/context-hydration-hook.mjs +89 -0
- package/scripts/demo-smoke.js +285 -0
- package/scripts/doctor.js +10 -8
- package/scripts/install.sh +33 -4
- package/scripts/install.sh.sha256 +1 -0
- package/scripts/linux-package-lib.mjs +0 -0
- package/scripts/linux-package-smoke.mjs +2 -2
- package/scripts/package-size-check.mjs +26 -0
- package/scripts/plugin-sync.js +83 -17
- package/scripts/postinstall.js +17 -10
- package/scripts/prepack.js +9 -0
- package/scripts/ralph/prompt.md +0 -0
- package/scripts/release-metadata.js +0 -0
- package/scripts/release-sync.js +72 -7
- package/scripts/release-verify.js +100 -7
- package/scripts/setup-npmrc.js +48 -23
- package/scripts/skills-install.js +9 -2
- package/scripts/skills-manifest.js +2 -21
- package/scripts/skills-pack.js +2 -2
- package/scripts/skills-validate.js +5 -55
- package/skills/align/SKILL.md +0 -0
- package/skills/align/agents/openai.yaml +0 -0
- package/skills/align/references/shared-language.md +0 -0
- package/skills/arch/SKILL.md +0 -0
- package/skills/arch/agents/openai.yaml +0 -0
- package/skills/build/SKILL.md +0 -0
- package/skills/build/agents/openai.yaml +0 -0
- package/skills/cleanup/SKILL.md +0 -0
- package/skills/cleanup/agents/openai.yaml +0 -0
- package/skills/cleanup/references/style-and-deslop.md +0 -0
- package/skills/diagnose/SKILL.md +0 -0
- package/skills/diagnose/agents/openai.yaml +0 -0
- package/skills/flow/SKILL.md +0 -0
- package/skills/flow/agents/openai.yaml +0 -0
- package/skills/index.json +0 -0
- package/skills/maestro/SKILL.md +0 -0
- package/skills/maestro/agents/openai.yaml +0 -0
- package/skills/maestro/references/core-release-rules.md +0 -0
- package/skills/maestro/references/native-ingest-map.md +0 -0
- package/skills/sage/SKILL.md +0 -0
- package/skills/sage/agents/openai.yaml +0 -0
- package/skills/sage/references/source-selection.md +0 -0
- package/skills/tdd/SKILL.md +0 -0
- package/skills/tdd/agents/openai.yaml +0 -0
- package/skills/vet/SKILL.md +0 -0
- package/skills/vet/agents/openai.yaml +0 -0
- package/skills/vet/references/security-playbooks.md +0 -0
- package/skills/vibe/SKILL.md +0 -0
- package/skills/vibe/agents/openai.yaml +0 -0
- package/sprint/00-idea.md +0 -0
- package/sprint/01-architecture.md +0 -0
- package/sprint/02-oss-evidence.md +0 -0
- package/sprint/03-logic.md +0 -0
- package/sprint/04-security.md +0 -0
- package/sprint/05-dx-ux.md +0 -0
- package/sprint/06-build-plan.md +0 -0
- package/sprint/07-release.md +0 -0
- package/src/agents.js +279 -0
- package/src/bootstrap.js +252 -63
- package/src/build-gate.js +0 -0
- package/src/codex-app-server.js +291 -0
- package/src/decision-log.js +0 -0
- package/src/fs-utils.js +175 -4
- package/src/launcher.js +24 -21
- package/src/mcp-config.js +9 -1
- package/src/mcp-live-client.js +140 -7
- package/src/paths.js +6 -1
- package/src/policy.js +2 -2
- package/src/prelaunch.js +189 -0
- package/src/quality/ai-quality-orchestrator.js +328 -0
- package/src/release-issue-gates.js +62 -0
- package/src/release-operations.js +62 -0
- package/src/release-state.js +0 -0
- package/src/runtime/active-autonomy-core.js +0 -0
- package/src/runtime/agent-compat.js +31 -0
- package/src/runtime/alignment-sentinel.js +0 -0
- package/src/runtime/architect-review.js +248 -10
- package/src/runtime/autonomous-tasks.js +408 -0
- package/src/runtime/build-readiness.js +0 -0
- package/src/runtime/code-graph-rehearse.js +0 -0
- package/src/runtime/codeburn-core.js +112 -0
- package/src/runtime/context-authority.js +40 -0
- package/src/runtime/context-economy-core.js +40 -0
- package/src/runtime/continuity-graph.js +201 -0
- package/src/runtime/continuity-notes.js +71 -26
- package/src/runtime/core-source-ingest.js +0 -0
- package/src/runtime/detached-provider.js +82 -14
- package/src/runtime/doctor-report.js +18 -0
- package/src/runtime/environment-awareness-core.js +0 -0
- package/src/runtime/exposure-catalog.js +0 -0
- package/src/runtime/graphify-core.js +167 -0
- package/src/runtime/guidance-stack.js +9 -2
- package/src/runtime/handoff-packets.js +92 -0
- package/src/runtime/headroom-core.js +104 -0
- package/src/runtime/helper-orchestration-core.js +0 -0
- package/src/runtime/hook-profiles.js +20 -0
- package/src/runtime/learning-loop-core.js +110 -4
- package/src/runtime/live-agent-verification.js +122 -0
- package/src/runtime/maestro-events.js +0 -0
- package/src/runtime/maestro-manager.js +0 -0
- package/src/runtime/maestro-output.js +58 -0
- package/src/runtime/maestro-state.js +0 -0
- package/src/runtime/managed-markdown.js +29 -0
- package/src/runtime/mcp-authority.js +147 -0
- package/src/runtime/mcp-policy.js +0 -0
- package/src/runtime/obsidian-integration-core.js +255 -17
- package/src/runtime/obsidian-plugin-bridge.js +344 -38
- package/src/runtime/orchestrator.js +0 -0
- package/src/runtime/pi-maestro-core.js +89 -0
- package/src/runtime/preferences.js +79 -0
- package/src/runtime/project-context.js +636 -0
- package/src/runtime/prompt-strategy-core.js +0 -0
- package/src/runtime/quorum-review.js +0 -0
- package/src/runtime/ralph-execution-core.js +0 -0
- package/src/runtime/redaction-gateway.js +218 -19
- package/src/runtime/runtime-state.js +94 -2
- package/src/runtime/schema-migrations.js +162 -0
- package/src/runtime/semantic-recording-core.js +0 -0
- package/src/runtime/signal-hooks.js +13 -0
- package/src/runtime/skills-registry-export.js +92 -8
- package/src/runtime/startup-path.js +0 -0
- package/src/runtime/task-contracts.js +83 -0
- package/src/runtime/universal-plugin-broker-core.js +373 -50
- package/src/runtime/workspace-intelligence-runtime.js +0 -0
- package/src/runtime/workspace-virtualizer.js +0 -0
- package/src/runtime/workspaces.js +0 -0
- package/src/runtime-artifacts.js +0 -0
- package/src/setup-lifecycle.js +359 -0
- package/src/skill-frontmatter.js +78 -0
- package/src/skill-installer.js +226 -71
- package/src/skills.js +297 -19
- package/src/state-sync.js +0 -0
- package/src/test-fixtures.js +543 -0
- package/src/tui/grid.js +67 -0
- package/src/tui/status-grid.js +23 -0
- package/support-bundle.json +114 -0
- package/templates/AGENTS.md +6 -2
- package/templates/catalog-manifest.json +0 -0
- package/templates/model-instructions/core.md +0 -0
- package/templates/model-instructions/release.md +0 -0
- package/templates/model-instructions/security.md +0 -0
- package/templates/quality/ai-quality-rules.yml +37 -0
- package/docs/assets/image/Screenshot(1).png +0 -0
- package/docs/assets/image/Screenshot(2).png +0 -0
- package/docs/assets/image/Screenshot(3).png +0 -0
- package/docs/assets/image/Screenshot(4).png +0 -0
- package/docs/assets/image/Screenshot(5).png +0 -0
- package/docs/assets/image/Screenshot(6).png +0 -0
- package/docs/assets/image/Screenshot(7).png +0 -0
- package/docs/assets/image/Screenshot(8).png +0 -0
- package/docs/assets/image/Screenshot(9).png +0 -0
- package/docs/assets/meta-architect-logo.png +0 -0
- package/docs/assets/meta-architect-logo.svg +0 -8
- package/docs/onboarding.md +0 -81
- package/docs/prompt-guidance-contract.md +0 -17
- package/docs/prompt-guidance-fragments/active-autonomy-core.md +0 -7
- package/docs/skills-publishing.md +0 -255
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { readJson, writeJson } from "../fs-utils.js";
|
|
7
|
+
import { getRepoRoot } from "../paths.js";
|
|
8
|
+
import { createFreshness } from "./context-authority.js";
|
|
9
|
+
import { createManagedMarkdownBlock, replaceManagedMarkdownBlock } from "./managed-markdown.js";
|
|
10
|
+
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
export const projectIndexSchemaVersion = "0.1.0";
|
|
13
|
+
const MAX_SOURCE_FILES = 2000;
|
|
14
|
+
const GENERATED_DIRS = [
|
|
15
|
+
"node_modules",
|
|
16
|
+
"dist",
|
|
17
|
+
"build",
|
|
18
|
+
".next",
|
|
19
|
+
".turbo",
|
|
20
|
+
".vercel",
|
|
21
|
+
".git",
|
|
22
|
+
".ma",
|
|
23
|
+
".codex",
|
|
24
|
+
".omx",
|
|
25
|
+
"coverage",
|
|
26
|
+
];
|
|
27
|
+
const VISIBLE_HIDDEN_DIRS = new Set([
|
|
28
|
+
".agents",
|
|
29
|
+
".claude",
|
|
30
|
+
".cline",
|
|
31
|
+
".cursor",
|
|
32
|
+
".gemini",
|
|
33
|
+
".github",
|
|
34
|
+
".openclaw",
|
|
35
|
+
".pi",
|
|
36
|
+
".roo",
|
|
37
|
+
".windsurf",
|
|
38
|
+
]);
|
|
39
|
+
const SECRET_NAMES = /(^|[._-])(env|secret|token|credential|password|private|key)([._-]|$)/i;
|
|
40
|
+
const LANGUAGE_BY_EXTENSION = {
|
|
41
|
+
".cjs": "javascript",
|
|
42
|
+
".css": "css",
|
|
43
|
+
".go": "go",
|
|
44
|
+
".java": "java",
|
|
45
|
+
".js": "javascript",
|
|
46
|
+
".jsx": "javascript",
|
|
47
|
+
".mjs": "javascript",
|
|
48
|
+
".py": "python",
|
|
49
|
+
".rb": "ruby",
|
|
50
|
+
".rs": "rust",
|
|
51
|
+
".sql": "sql",
|
|
52
|
+
".ts": "typescript",
|
|
53
|
+
".tsx": "typescript",
|
|
54
|
+
".vue": "vue",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function getProjectIndexPath(root = getRepoRoot()) {
|
|
58
|
+
return path.join(root, ".ma", "context", "project-index.json");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function refreshProjectIndex(root = getRepoRoot(), { mode = "incremental" } = {}) {
|
|
62
|
+
const previous = await readJson(getProjectIndexPath(root)).catch(() => null);
|
|
63
|
+
const { files: sourceFiles, incompleteScan } = await collectSourceFiles(root);
|
|
64
|
+
const sourceHash = hashSourceFiles(sourceFiles);
|
|
65
|
+
const changedFiles = diffSourceFiles(previous?.sourceFiles, sourceFiles);
|
|
66
|
+
const packageData = await readPackageJson(root);
|
|
67
|
+
const index = {
|
|
68
|
+
schemaVersion: projectIndexSchemaVersion,
|
|
69
|
+
record_type: "project_index",
|
|
70
|
+
authority: "source_truth",
|
|
71
|
+
source: "repository-filesystem",
|
|
72
|
+
freshness: createFreshness({
|
|
73
|
+
status: previous?.freshness?.sourceHash === sourceHash ? "unchanged" : "fresh",
|
|
74
|
+
sourceHash,
|
|
75
|
+
sourceFiles: changedFiles,
|
|
76
|
+
}),
|
|
77
|
+
quality: createContextQuality({ sourceFiles, incompleteScan }),
|
|
78
|
+
project: {
|
|
79
|
+
name: packageData?.name ?? path.basename(path.resolve(root)),
|
|
80
|
+
description: packageData?.description ?? null,
|
|
81
|
+
stack: detectStack(packageData),
|
|
82
|
+
},
|
|
83
|
+
languages: detectLanguages(sourceFiles),
|
|
84
|
+
frameworks: detectFrameworks(packageData),
|
|
85
|
+
packageManager: detectPackageManager(sourceFiles),
|
|
86
|
+
commands: sanitizeCommands(packageData?.scripts ?? {}),
|
|
87
|
+
entrypoints: detectEntrypoints(sourceFiles),
|
|
88
|
+
importantDocs: detectImportantDocs(sourceFiles),
|
|
89
|
+
gitRemote: sanitizeRemote(await readGitValue(root, ["config", "--get", "remote.origin.url"])),
|
|
90
|
+
defaultBranch: await readGitValue(root, [
|
|
91
|
+
"symbolic-ref",
|
|
92
|
+
"--short",
|
|
93
|
+
"refs/remotes/origin/HEAD",
|
|
94
|
+
]),
|
|
95
|
+
generatedDirectories: GENERATED_DIRS,
|
|
96
|
+
vendorIntegrations: detectVendorIntegrations(sourceFiles),
|
|
97
|
+
workspaces: await detectWorkspaces(root, packageData, sourceFiles),
|
|
98
|
+
nestedRepositories: await detectNestedRepositories(root),
|
|
99
|
+
facts: [],
|
|
100
|
+
knownGaps: Array.isArray(previous?.humanOverrides?.knownGaps)
|
|
101
|
+
? previous.humanOverrides.knownGaps
|
|
102
|
+
: [],
|
|
103
|
+
sourceFiles,
|
|
104
|
+
humanOverrides: previous?.humanOverrides ?? {},
|
|
105
|
+
};
|
|
106
|
+
const conflicts = findHumanOverrideConflicts(index);
|
|
107
|
+
if (conflicts.length > 0) {
|
|
108
|
+
index.quality = {
|
|
109
|
+
...index.quality,
|
|
110
|
+
confidence: "conflicting",
|
|
111
|
+
gaps: [
|
|
112
|
+
...index.quality.gaps,
|
|
113
|
+
...conflicts.map((entry) => `human correction conflicts with source: ${entry}`),
|
|
114
|
+
],
|
|
115
|
+
conflicts,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
index.context = {
|
|
119
|
+
authority: "source_truth",
|
|
120
|
+
source: "repository-filesystem",
|
|
121
|
+
freshness: index.freshness,
|
|
122
|
+
provenance: sourceFiles.map((file) => file.path),
|
|
123
|
+
};
|
|
124
|
+
index.facts = createCanonicalFacts(index);
|
|
125
|
+
const affectedArtifacts = getAffectedContextArtifacts(changedFiles);
|
|
126
|
+
await writeJson(getProjectIndexPath(root), index);
|
|
127
|
+
if (mode === "full" || !previous || changedFiles.length > 0) {
|
|
128
|
+
await writeAgentContextArtifacts(root, index);
|
|
129
|
+
}
|
|
130
|
+
await writeJson(path.join(root, ".ma", "context", "refresh-receipt.json"), {
|
|
131
|
+
schemaVersion: projectIndexSchemaVersion,
|
|
132
|
+
record_type: "project_context_refresh",
|
|
133
|
+
mode,
|
|
134
|
+
changedFiles,
|
|
135
|
+
affectedArtifacts,
|
|
136
|
+
staleDerivedArtifacts: changedFiles.length > 0 ? affectedArtifacts : [],
|
|
137
|
+
conflicts,
|
|
138
|
+
refreshedAt: index.freshness.checkedAt,
|
|
139
|
+
});
|
|
140
|
+
return index;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getAffectedContextArtifacts(changedFiles) {
|
|
144
|
+
if (changedFiles.length === 0) return [];
|
|
145
|
+
const artifacts = new Set(["project-index.json", "agent-brief.md", "architecture.md"]);
|
|
146
|
+
if (changedFiles.some((file) => file === "package.json" || file.endsWith("/package.json"))) {
|
|
147
|
+
artifacts.add("commands.json");
|
|
148
|
+
}
|
|
149
|
+
return [...artifacts];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function writeAgentContextArtifacts(root, index) {
|
|
153
|
+
const contextDir = path.join(root, ".ma", "context");
|
|
154
|
+
await fs.mkdir(contextDir, { recursive: true });
|
|
155
|
+
await writeJson(path.join(contextDir, "commands.json"), {
|
|
156
|
+
schemaVersion: projectIndexSchemaVersion,
|
|
157
|
+
record_type: "project_commands",
|
|
158
|
+
authority: "source_truth",
|
|
159
|
+
source: "package.json",
|
|
160
|
+
freshness: index.freshness,
|
|
161
|
+
commands: index.commands,
|
|
162
|
+
});
|
|
163
|
+
const brief = [
|
|
164
|
+
`# ${index.project.name}`,
|
|
165
|
+
"",
|
|
166
|
+
`Authority: ${index.authority} | Freshness: ${index.freshness.status}`,
|
|
167
|
+
`Stack: ${index.project.stack.join(", ") || "unknown"}`,
|
|
168
|
+
`Languages: ${index.languages.join(", ") || "unknown"}`,
|
|
169
|
+
`Commands: ${Object.keys(index.commands).join(", ") || "none detected"}`,
|
|
170
|
+
"First-read: .ma/context/project-index.json",
|
|
171
|
+
"",
|
|
172
|
+
"## Instructions",
|
|
173
|
+
"",
|
|
174
|
+
"Read the source files and configured commands before trusting generated context.",
|
|
175
|
+
"",
|
|
176
|
+
].join("\n");
|
|
177
|
+
await writeManagedMarkdown(
|
|
178
|
+
path.join(contextDir, "agent-brief.md"),
|
|
179
|
+
createManagedMarkdownBlock({
|
|
180
|
+
id: "agent-brief",
|
|
181
|
+
source: ".ma/context/project-index.json",
|
|
182
|
+
body: brief,
|
|
183
|
+
}),
|
|
184
|
+
"agent-brief",
|
|
185
|
+
);
|
|
186
|
+
const architecture = [
|
|
187
|
+
`# ${index.project.name} Architecture Map`,
|
|
188
|
+
"",
|
|
189
|
+
`Entrypoints: ${index.entrypoints.join(", ") || "none detected"}`,
|
|
190
|
+
`Important docs: ${index.importantDocs.join(", ") || "none detected"}`,
|
|
191
|
+
`Workspaces: ${index.workspaces.packages.length}`,
|
|
192
|
+
"",
|
|
193
|
+
].join("\n");
|
|
194
|
+
await writeManagedMarkdown(
|
|
195
|
+
path.join(contextDir, "architecture.md"),
|
|
196
|
+
createManagedMarkdownBlock({
|
|
197
|
+
id: "architecture",
|
|
198
|
+
source: ".ma/context/project-index.json",
|
|
199
|
+
body: architecture,
|
|
200
|
+
}),
|
|
201
|
+
"architecture",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function writeManagedMarkdown(filePath, generated, id) {
|
|
206
|
+
const existing = await fs.readFile(filePath, "utf8").catch(() => null);
|
|
207
|
+
if (!existing) {
|
|
208
|
+
await fs.writeFile(filePath, generated, "utf8");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
await fs.writeFile(filePath, replaceManagedMarkdownBlock(existing, generated, id), "utf8");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function loadProjectIndex(root = getRepoRoot()) {
|
|
215
|
+
const index = await readJson(getProjectIndexPath(root));
|
|
216
|
+
const qualityNeedsMigration =
|
|
217
|
+
!index.quality ||
|
|
218
|
+
!Array.isArray(index.quality.verification) ||
|
|
219
|
+
!Array.isArray(index.quality.gaps);
|
|
220
|
+
const quality = index.quality
|
|
221
|
+
? {
|
|
222
|
+
...index.quality,
|
|
223
|
+
verification: Array.isArray(index.quality.verification)
|
|
224
|
+
? index.quality.verification
|
|
225
|
+
: ["repository filesystem scan"],
|
|
226
|
+
gaps: Array.isArray(index.quality.gaps) ? index.quality.gaps : [],
|
|
227
|
+
}
|
|
228
|
+
: createContextQuality({ sourceFiles: index.sourceFiles ?? [] });
|
|
229
|
+
if (!Array.isArray(index.facts) || qualityNeedsMigration) {
|
|
230
|
+
const migrated = {
|
|
231
|
+
...index,
|
|
232
|
+
quality,
|
|
233
|
+
facts: Array.isArray(index.facts) ? index.facts : createCanonicalFacts(index),
|
|
234
|
+
};
|
|
235
|
+
await writeJson(getProjectIndexPath(root), migrated);
|
|
236
|
+
return validateProjectIndex(migrated);
|
|
237
|
+
}
|
|
238
|
+
return validateProjectIndex(index);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function validateProjectIndex(value) {
|
|
242
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
243
|
+
throw new Error("project index must be an object");
|
|
244
|
+
}
|
|
245
|
+
if (value.schemaVersion !== projectIndexSchemaVersion) {
|
|
246
|
+
throw new Error(`Unsupported project index schemaVersion: ${value.schemaVersion}`);
|
|
247
|
+
}
|
|
248
|
+
if (value.record_type !== "project_index" || value.authority !== "source_truth") {
|
|
249
|
+
throw new Error("project index has invalid authority metadata");
|
|
250
|
+
}
|
|
251
|
+
if (!value.freshness?.sourceHash || !Array.isArray(value.sourceFiles)) {
|
|
252
|
+
throw new Error("project index requires source freshness metadata");
|
|
253
|
+
}
|
|
254
|
+
if (!value.quality?.completeness || !value.quality?.confidence || !value.quality?.coverage) {
|
|
255
|
+
throw new Error("project index requires quality metadata");
|
|
256
|
+
}
|
|
257
|
+
if (!["complete", "partial", "minimal", "unknown"].includes(value.quality.completeness)) {
|
|
258
|
+
throw new Error("project index has invalid completeness metadata");
|
|
259
|
+
}
|
|
260
|
+
if (!["verified", "inferred", "stale", "conflicting"].includes(value.quality.confidence)) {
|
|
261
|
+
throw new Error("project index has invalid confidence metadata");
|
|
262
|
+
}
|
|
263
|
+
if (!Array.isArray(value.quality.verification) || !Array.isArray(value.quality.gaps)) {
|
|
264
|
+
throw new Error("project index quality requires verification and gaps");
|
|
265
|
+
}
|
|
266
|
+
if (
|
|
267
|
+
!Array.isArray(value.facts) ||
|
|
268
|
+
value.facts.some((fact) => !fact.id || fact.authority !== "source_truth")
|
|
269
|
+
) {
|
|
270
|
+
throw new Error("project index requires canonical source-truth facts");
|
|
271
|
+
}
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function createContextQuality({ sourceFiles = [], incompleteScan = false, freshness } = {}) {
|
|
276
|
+
const gaps = [
|
|
277
|
+
...(sourceFiles.length >= MAX_SOURCE_FILES
|
|
278
|
+
? [`source scan capped at ${MAX_SOURCE_FILES} files`]
|
|
279
|
+
: []),
|
|
280
|
+
...(incompleteScan ? ["one or more source directories could not be read"] : []),
|
|
281
|
+
];
|
|
282
|
+
return {
|
|
283
|
+
completeness: sourceFiles.length === 0 ? "minimal" : gaps.length > 0 ? "partial" : "complete",
|
|
284
|
+
confidence: freshness?.stale ? "stale" : incompleteScan ? "inferred" : "verified",
|
|
285
|
+
coverage: {
|
|
286
|
+
sourceFiles: sourceFiles.length,
|
|
287
|
+
capped: sourceFiles.length >= MAX_SOURCE_FILES,
|
|
288
|
+
scan: incompleteScan ? "incomplete" : "complete",
|
|
289
|
+
},
|
|
290
|
+
verification: ["repository filesystem scan"],
|
|
291
|
+
gaps,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function deduplicateCanonicalFacts(facts = []) {
|
|
296
|
+
const seen = new Set();
|
|
297
|
+
return facts.filter((fact) => {
|
|
298
|
+
const key = fact?.id ?? JSON.stringify([fact?.kind, fact?.key, fact?.value]);
|
|
299
|
+
if (seen.has(key)) return false;
|
|
300
|
+
seen.add(key);
|
|
301
|
+
return true;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function findHumanOverrideConflicts(index) {
|
|
306
|
+
const corrections = index.humanOverrides?.corrections;
|
|
307
|
+
if (!corrections || typeof corrections !== "object") return [];
|
|
308
|
+
const conflicts = [];
|
|
309
|
+
for (const [section, values] of Object.entries(corrections)) {
|
|
310
|
+
if (!values || typeof values !== "object") continue;
|
|
311
|
+
for (const [key, value] of Object.entries(values)) {
|
|
312
|
+
const source = index[section]?.[key] ?? index[section === "project" ? "project" : section];
|
|
313
|
+
if (typeof value === "string" && source && source[key] !== value)
|
|
314
|
+
conflicts.push(`${section}.${key}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return conflicts;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function collectSourceFiles(root) {
|
|
321
|
+
const files = [];
|
|
322
|
+
let incompleteScan = false;
|
|
323
|
+
async function walk(current, relativeDirectory = "") {
|
|
324
|
+
if (files.length >= MAX_SOURCE_FILES) return;
|
|
325
|
+
const entries = await fs.readdir(current, { withFileTypes: true }).catch((error) => {
|
|
326
|
+
if (error?.code === "ENOENT") return [];
|
|
327
|
+
incompleteScan = true;
|
|
328
|
+
return [];
|
|
329
|
+
});
|
|
330
|
+
for (const entry of entries) {
|
|
331
|
+
if (files.length >= MAX_SOURCE_FILES) break;
|
|
332
|
+
if (entry.name.startsWith(".") && !VISIBLE_HIDDEN_DIRS.has(entry.name)) continue;
|
|
333
|
+
if (SECRET_NAMES.test(entry.name)) continue;
|
|
334
|
+
const relative = path.join(relativeDirectory, entry.name);
|
|
335
|
+
if (entry.isDirectory()) {
|
|
336
|
+
if (!GENERATED_DIRS.includes(entry.name))
|
|
337
|
+
await walk(path.join(current, entry.name), relative);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (!entry.isFile()) continue;
|
|
341
|
+
const stat = await fs.stat(path.join(current, entry.name)).catch((error) => {
|
|
342
|
+
if (error?.code === "ENOENT") return null;
|
|
343
|
+
incompleteScan = true;
|
|
344
|
+
return null;
|
|
345
|
+
});
|
|
346
|
+
if (!stat) continue;
|
|
347
|
+
files.push({
|
|
348
|
+
path: relative.split(path.sep).join("/"),
|
|
349
|
+
size: stat.size,
|
|
350
|
+
mtimeMs: Math.floor(stat.mtimeMs),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
await walk(root);
|
|
355
|
+
return {
|
|
356
|
+
files: files.sort((left, right) => left.path.localeCompare(right.path)),
|
|
357
|
+
incompleteScan,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function readPackageJson(root) {
|
|
362
|
+
return readJson(path.join(root, "package.json")).catch(() => null);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function detectWorkspaces(root, packageData, sourceFiles) {
|
|
366
|
+
const declared = Array.isArray(packageData?.workspaces)
|
|
367
|
+
? packageData.workspaces
|
|
368
|
+
: Array.isArray(packageData?.workspaces?.packages)
|
|
369
|
+
? packageData.workspaces.packages
|
|
370
|
+
: [];
|
|
371
|
+
const packagePaths = new Set(["package.json"]);
|
|
372
|
+
for (const file of sourceFiles) {
|
|
373
|
+
if (file.path.endsWith("/package.json")) packagePaths.add(file.path);
|
|
374
|
+
}
|
|
375
|
+
const workspacePaths =
|
|
376
|
+
declared.length > 0 ? declared : [...packagePaths].filter((file) => file !== "package.json");
|
|
377
|
+
const packages = [];
|
|
378
|
+
for (const relativePackagePath of packagePaths) {
|
|
379
|
+
if (relativePackagePath === "package.json") continue;
|
|
380
|
+
const packagePath = path.join(root, relativePackagePath);
|
|
381
|
+
const relativeDirectory = path.posix.dirname(relativePackagePath);
|
|
382
|
+
if (
|
|
383
|
+
declared.length > 0 &&
|
|
384
|
+
!workspacePaths.some((pattern) => matchesWorkspacePattern(relativeDirectory, pattern))
|
|
385
|
+
) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const data = await readJson(packagePath).catch(() => null);
|
|
389
|
+
if (!data) continue;
|
|
390
|
+
packages.push({
|
|
391
|
+
path: relativeDirectory,
|
|
392
|
+
name: data.name ?? relativeDirectory,
|
|
393
|
+
commands: sanitizeCommands(data.scripts ?? {}),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
declared: declared.length > 0,
|
|
398
|
+
packages: packages.sort((left, right) => left.path.localeCompare(right.path)),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function matchesWorkspacePattern(relativeDirectory, pattern) {
|
|
403
|
+
const normalized = String(pattern).replaceAll("\\", "/").replace(/\/$/, "");
|
|
404
|
+
if (normalized.endsWith("/*")) return relativeDirectory.startsWith(`${normalized.slice(0, -2)}/`);
|
|
405
|
+
return relativeDirectory === normalized;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function detectNestedRepositories(root) {
|
|
409
|
+
const found = [];
|
|
410
|
+
async function walk(current, relativeDirectory = "") {
|
|
411
|
+
if (found.length >= 100) return;
|
|
412
|
+
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
413
|
+
for (const entry of entries) {
|
|
414
|
+
if (entry.name === ".git" || GENERATED_DIRS.includes(entry.name)) continue;
|
|
415
|
+
if (!entry.isDirectory()) continue;
|
|
416
|
+
const relative = path.join(relativeDirectory, entry.name).split(path.sep).join("/");
|
|
417
|
+
if (await directoryExists(path.join(current, entry.name, ".git"))) found.push(relative);
|
|
418
|
+
await walk(path.join(current, entry.name), relative);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
await walk(root);
|
|
422
|
+
return found.sort();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function directoryExists(target) {
|
|
426
|
+
try {
|
|
427
|
+
return (await fs.stat(target)).isDirectory();
|
|
428
|
+
} catch {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function readGitValue(root, args) {
|
|
434
|
+
try {
|
|
435
|
+
const result = await execFileAsync("git", args, {
|
|
436
|
+
cwd: root,
|
|
437
|
+
timeout: 1000,
|
|
438
|
+
maxBuffer: 16 * 1024,
|
|
439
|
+
});
|
|
440
|
+
return result.stdout.trim().replace(/^refs\/remotes\/origin\//, "") || null;
|
|
441
|
+
} catch {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function detectLanguages(files) {
|
|
447
|
+
return [
|
|
448
|
+
...new Set(
|
|
449
|
+
files
|
|
450
|
+
.map((file) => LANGUAGE_BY_EXTENSION[path.extname(file.path).toLowerCase()])
|
|
451
|
+
.filter(Boolean),
|
|
452
|
+
),
|
|
453
|
+
].sort();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function detectStack(packageData) {
|
|
457
|
+
return [
|
|
458
|
+
...new Set([
|
|
459
|
+
...(packageData?.dependencies ? Object.keys(packageData.dependencies) : []),
|
|
460
|
+
...(packageData?.devDependencies ? Object.keys(packageData.devDependencies) : []),
|
|
461
|
+
]),
|
|
462
|
+
]
|
|
463
|
+
.filter((name) =>
|
|
464
|
+
[
|
|
465
|
+
"next",
|
|
466
|
+
"react",
|
|
467
|
+
"vue",
|
|
468
|
+
"svelte",
|
|
469
|
+
"typescript",
|
|
470
|
+
"vite",
|
|
471
|
+
"express",
|
|
472
|
+
"fastify",
|
|
473
|
+
"trpc",
|
|
474
|
+
"zod",
|
|
475
|
+
].includes(name),
|
|
476
|
+
)
|
|
477
|
+
.sort();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function detectFrameworks(packageData) {
|
|
481
|
+
return detectStack(packageData).filter((name) => !["typescript", "zod", "trpc"].includes(name));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function detectPackageManager(files) {
|
|
485
|
+
if (files.some((file) => file.path === "pnpm-lock.yaml")) return "pnpm";
|
|
486
|
+
if (files.some((file) => file.path === "yarn.lock")) return "yarn";
|
|
487
|
+
if (files.some((file) => file.path === "bun.lockb" || file.path === "bun.lock")) return "bun";
|
|
488
|
+
if (files.some((file) => file.path === "package-lock.json")) return "npm";
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function sanitizeCommands(commands) {
|
|
493
|
+
return Object.fromEntries(
|
|
494
|
+
Object.entries(commands).map(([name, command]) => [name, sanitizeText(command)]),
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function sanitizeText(value) {
|
|
499
|
+
return String(value).replace(
|
|
500
|
+
/((?:^|\s)[A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PRIVATE|CREDENTIAL|KEY)[A-Z0-9_]*=)(?:"[^"]*"|'[^']*'|\S+)/gi,
|
|
501
|
+
"$1[REDACTED]",
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function sanitizeRemote(value) {
|
|
506
|
+
if (!value) return null;
|
|
507
|
+
try {
|
|
508
|
+
const remote = new URL(value);
|
|
509
|
+
remote.username = "";
|
|
510
|
+
remote.password = "";
|
|
511
|
+
for (const key of [...remote.searchParams.keys()]) {
|
|
512
|
+
if (/(token|secret|password|key|credential|auth)/i.test(key)) remote.searchParams.delete(key);
|
|
513
|
+
}
|
|
514
|
+
return remote.toString().replace(/\/$/, "");
|
|
515
|
+
} catch {
|
|
516
|
+
return value.replace(/:[^/@\s]+@/, ":[REDACTED]@");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function detectEntrypoints(files) {
|
|
521
|
+
return files
|
|
522
|
+
.map((file) => file.path)
|
|
523
|
+
.filter((file) => /^(src\/)?(index|main|cli)\.(c?m?js|ts|tsx)$/.test(file))
|
|
524
|
+
.slice(0, 20);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function detectImportantDocs(files) {
|
|
528
|
+
const known = new Set([
|
|
529
|
+
"README.md",
|
|
530
|
+
"AGENTS.md",
|
|
531
|
+
"CLAUDE.md",
|
|
532
|
+
"GEMINI.md",
|
|
533
|
+
"CONTRIBUTING.md",
|
|
534
|
+
"docs/architecture.md",
|
|
535
|
+
]);
|
|
536
|
+
return files.map((file) => file.path).filter((file) => known.has(file));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function detectVendorIntegrations(files) {
|
|
540
|
+
const paths = files.map((file) => file.path);
|
|
541
|
+
const matches = [];
|
|
542
|
+
if (paths.some((file) => file === "AGENTS.md" || file.startsWith(".codex/")))
|
|
543
|
+
matches.push("codex-cli");
|
|
544
|
+
if (paths.some((file) => file === "CLAUDE.md" || file.startsWith(".claude/")))
|
|
545
|
+
matches.push("claude-code");
|
|
546
|
+
if (paths.some((file) => file.startsWith(".cursor/"))) matches.push("cursor");
|
|
547
|
+
if (paths.some((file) => file === "GEMINI.md" || file.startsWith(".gemini/")))
|
|
548
|
+
matches.push("gemini-cli");
|
|
549
|
+
if (paths.some((file) => file.startsWith(".openclaw/") || file === "openclaw.json"))
|
|
550
|
+
matches.push("openclaw");
|
|
551
|
+
if (paths.some((file) => file.startsWith(".pi/"))) matches.push("pi");
|
|
552
|
+
return matches;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function hashSourceFiles(files) {
|
|
556
|
+
return createHash("sha256").update(JSON.stringify(files)).digest("hex");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function createCanonicalFacts(index) {
|
|
560
|
+
const candidates = [
|
|
561
|
+
["project", "name", index.project.name],
|
|
562
|
+
["project", "stack", index.project.stack],
|
|
563
|
+
["project", "languages", index.languages],
|
|
564
|
+
["project", "frameworks", index.frameworks],
|
|
565
|
+
["project", "package_manager", index.packageManager],
|
|
566
|
+
["project", "commands", index.commands],
|
|
567
|
+
["project", "vendors", index.vendorIntegrations],
|
|
568
|
+
["project", "workspaces", index.workspaces],
|
|
569
|
+
];
|
|
570
|
+
return deduplicateCanonicalFacts(
|
|
571
|
+
candidates.map(([kind, key, value]) => {
|
|
572
|
+
const normalized = stableValue({ kind, key, value });
|
|
573
|
+
const id = `fact-${createHash("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 16)}`;
|
|
574
|
+
return {
|
|
575
|
+
id,
|
|
576
|
+
kind,
|
|
577
|
+
key,
|
|
578
|
+
value,
|
|
579
|
+
authority: "source_truth",
|
|
580
|
+
source: "repository-filesystem",
|
|
581
|
+
sourceHash: index.freshness.sourceHash,
|
|
582
|
+
provenance: {
|
|
583
|
+
type: key === "commands" ? "command" : "file",
|
|
584
|
+
paths: factSourcePaths(index, key),
|
|
585
|
+
checkedAt: index.freshness.checkedAt,
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function factSourcePaths(index, key) {
|
|
593
|
+
if (["name", "stack", "commands", "package_manager"].includes(key)) {
|
|
594
|
+
return index.sourceFiles
|
|
595
|
+
.filter(
|
|
596
|
+
(file) =>
|
|
597
|
+
file.path === "package.json" ||
|
|
598
|
+
/package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$|bun\.lockb$/.test(file.path),
|
|
599
|
+
)
|
|
600
|
+
.map((file) => file.path)
|
|
601
|
+
.sort((left, right) =>
|
|
602
|
+
left === "package.json" ? -1 : right === "package.json" ? 1 : left.localeCompare(right),
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
if (key === "vendors")
|
|
606
|
+
return index.sourceFiles
|
|
607
|
+
.filter((file) =>
|
|
608
|
+
/^(AGENTS\.md|CLAUDE\.md|GEMINI\.md|openclaw\.json|\.(codex|claude|cursor|gemini|openclaw|pi)\/)/.test(
|
|
609
|
+
file.path,
|
|
610
|
+
),
|
|
611
|
+
)
|
|
612
|
+
.map((file) => file.path);
|
|
613
|
+
return index.sourceFiles.map((file) => file.path);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function stableValue(value) {
|
|
617
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
618
|
+
if (!value || typeof value !== "object") return value;
|
|
619
|
+
return Object.fromEntries(
|
|
620
|
+
Object.keys(value)
|
|
621
|
+
.sort()
|
|
622
|
+
.map((key) => [key, stableValue(value[key])]),
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function diffSourceFiles(previous, current) {
|
|
627
|
+
const oldFiles = new Map(
|
|
628
|
+
(previous ?? []).map((file) => [file.path, `${file.size}:${file.mtimeMs}`]),
|
|
629
|
+
);
|
|
630
|
+
const changed = [];
|
|
631
|
+
for (const file of current) {
|
|
632
|
+
if (oldFiles.get(file.path) !== `${file.size}:${file.mtimeMs}`) changed.push(file.path);
|
|
633
|
+
oldFiles.delete(file.path);
|
|
634
|
+
}
|
|
635
|
+
return changed.concat([...oldFiles.keys()].map((file) => `deleted:${file}`)).slice(0, 100);
|
|
636
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|