@inneranimalmedia/agentsam-sdk 2.4.1 → 2.5.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/README.md +7 -6
- package/docs/AGENTSAM_WORKBENCH.md +30 -0
- package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
- package/docs/CAPABILITIES.md +5 -3
- package/docs/CLI_SHELL.md +57 -80
- package/docs/CMS_STUDIO.md +101 -0
- package/docs/CONTEXT.md +170 -0
- package/docs/LOCAL_OLLAMA.md +58 -0
- package/docs/MERKLE.md +19 -19
- package/docs/PORTABLE_CONTEXT.md +4 -3
- package/docs/PROJECT_CONFIG.md +72 -0
- package/docs/RELEASES.md +26 -2
- package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
- package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
- package/docs/SDK_WORKER.md +86 -0
- package/docs/SECURITY.md +59 -22
- package/docs/client-cms-editor.md +15 -0
- package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
- package/package.json +17 -4
- package/packages/identity/package.json +11 -2
- package/packages/identity/src/contracts/auth-config.js +98 -0
- package/packages/identity/src/index.js +1 -0
- package/packages/identity/src/oauth/README.md +2 -2
- package/packages/identity/src/oauth/credentials.js +11 -4
- package/packages/identity/src/oauth/iam-platform.js +3 -3
- package/packages/identity/src/providers/iam/index.js +7 -7
- package/packages/identity/src/providers/iam/oauth.js +6 -4
- package/packages/identity/src/providers/iam/profile.js +5 -5
- package/packages/identity/tests/auth-config.test.mjs +57 -0
- package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
- package/protocol/FILEMETA_V1.md +95 -0
- package/protocol/INSPECT_VIEWS_V1.md +40 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
- package/protocol/MERKLE_V1.md +3 -1
- package/protocol/capabilities/manifest.json +32 -3
- package/protocol/capabilities/repository-snapshot.schema.json +1 -0
- package/protocol/context/context-budget.schema.json +41 -0
- package/protocol/context/context-item.schema.json +20 -0
- package/protocol/context/resolved-context-pack.schema.json +42 -0
- package/protocol/context/result-policy.schema.json +17 -0
- package/protocol/knowledge/context-pack.schema.json +33 -0
- package/protocol/knowledge/retrieval-query.schema.json +68 -13
- package/python/README.md +15 -7
- package/python/agentsam_sdk/cli.py +0 -21
- package/python/agentsam_sdk/tui/README.md +17 -12
- package/python/agentsam_sdk/tui/bootstrap.py +2 -2
- package/python/agentsam_sdk/tui/demo.py +25 -10
- package/python/agentsam_sdk/tui/onboarding.py +208 -0
- package/python/tests/test_tui_cli.py +7 -6
- package/skills/README.md +21 -0
- package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
- package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
- package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
- package/skills/agentsam-jr-dev/SKILL.md +232 -0
- package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
- package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
- package/skills/agentsam-progression-guard/SKILL.md +197 -0
- package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
- package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
- package/skills/catalog.json +53 -0
- package/src/capabilities/index.js +7 -0
- package/src/capabilities/repository-snapshot-view.js +238 -0
- package/src/capabilities/repository-snapshot.js +28 -14
- package/src/cli.js +108 -56
- package/src/commands/context.js +14 -2
- package/src/commands/db.js +4 -7
- package/src/commands/deploy.js +17 -31
- package/src/commands/interactive.js +21 -0
- package/src/commands/knowledge.js +27 -4
- package/src/commands/merkle-persist.js +118 -0
- package/src/commands/merkle.js +32 -17
- package/src/commands/models.js +107 -0
- package/src/commands/ollama.js +259 -0
- package/src/commands/preferences.js +102 -0
- package/src/commands/product.js +86 -16
- package/src/commands/security.js +3 -3
- package/src/commands/shell.js +71 -27
- package/src/commands/skills.js +66 -0
- package/src/commands/start-local.js +1 -1
- package/src/commands/tunnel.js +5 -4
- package/src/context/budget.js +58 -0
- package/src/context/compact.js +28 -0
- package/src/context/index.js +5 -0
- package/src/context/resolve.js +84 -0
- package/src/context/result-policy.js +66 -0
- package/src/index.js +16 -0
- package/src/indexing/execution-boundary.js +144 -0
- package/src/indexing/index.js +8 -0
- package/src/indexing/provider.js +41 -0
- package/src/knowledge/config.js +2 -2
- package/src/knowledge/context-pack.js +12 -1
- package/src/knowledge/contracts.js +9 -4
- package/src/knowledge/engine.js +7 -3
- package/src/knowledge/service/server.js +1 -1
- package/src/lib/bridge-client.js +7 -5
- package/src/lib/cli-preferences.js +74 -0
- package/src/lib/core-client.js +8 -8
- package/src/lib/deploy-receipt/index.js +5 -2
- package/src/lib/detect-context.js +6 -5
- package/src/lib/identity-scaffold.js +1 -1
- package/src/lib/local-scaffold.js +35 -33
- package/src/lib/local-status.js +9 -17
- package/src/lib/merkle/cloudflare-persistence.js +321 -0
- package/src/lib/merkle/filemeta.js +43 -0
- package/src/lib/merkle/git-ignore.js +24 -0
- package/src/lib/merkle/hash.js +1 -0
- package/src/lib/merkle/index.js +22 -0
- package/src/lib/merkle/persistence.js +72 -0
- package/src/lib/merkle/semantic.js +359 -0
- package/src/lib/merkle/snapshot.js +9 -3
- package/src/lib/merkle/tree.js +11 -6
- package/src/lib/project-config.js +227 -0
- package/src/lib/project-rules.js +68 -0
- package/src/lib/save-sdk-token.js +1 -1
- package/src/lib/slash-commands.js +2 -1
- package/src/lib/tools.js +11 -5
- package/src/security/index.js +1 -0
- package/src/security/inventory.js +4 -1
- package/src/security/render.js +27 -5
- package/src/security/scan.js +24 -9
- package/src/security/trust-boundary.js +24 -0
- package/src/skills/index.js +64 -0
- package/src/tools/index.js +1 -0
- package/src/tools/search.js +70 -0
- package/src/ui/ansi.js +1 -1
- package/src/ui/boot.js +56 -0
- package/src/ui/merkle/render.js +1 -0
- package/src/ui/runtime-activity.js +192 -0
- package/src/ui/theme.js +19 -18
- package/test/app-building-skills.test.mjs +61 -0
- package/test/apps-scaffold-contract.test.mjs +56 -0
- package/test/capabilities.test.mjs +53 -4
- package/test/cli-preferences.test.mjs +25 -0
- package/test/context.test.mjs +95 -0
- package/test/indexing-provider.test.mjs +29 -0
- package/test/jr-dev-skill.test.mjs +26 -0
- package/test/knowledge-context-pack.test.mjs +19 -0
- package/test/knowledge.test.mjs +1 -1
- package/test/merkle-persistence.test.mjs +91 -0
- package/test/merkle.test.mjs +77 -3
- package/test/models.test.mjs +37 -0
- package/test/ollama.test.mjs +94 -0
- package/test/project-config.test.mjs +81 -0
- package/test/project-rules.test.mjs +44 -0
- package/test/release-hygiene.test.mjs +34 -0
- package/test/repository-snapshot-view.test.mjs +112 -0
- package/test/runtime-activity.test.mjs +98 -0
- package/test/sdk-worker-contract.test.mjs +68 -0
- package/test/security.test.mjs +46 -0
- package/test/shell.test.mjs +8 -1
- package/test/skills.test.mjs +22 -0
- package/test/smoke.mjs +2 -2
- package/test/theme-portability.test.mjs +14 -0
- package/test/tools-search.test.mjs +27 -0
- package/examples/agentsam-tui-ansi.mjs +0 -149
- package/src/commands/tui.js +0 -120
- package/src/ui/splash-xterm.js +0 -290
- package/src/ui/splash.js +0 -426
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Hooks and Operational I/O
|
|
2
|
+
|
|
3
|
+
## A hook is a boundary-triggered program
|
|
4
|
+
|
|
5
|
+
Examples include Git hooks, CI jobs, deploy hooks, webhooks, queue consumers,
|
|
6
|
+
scheduled tasks, post-migration checks, and runtime alerts. The triggering mechanism
|
|
7
|
+
changes; the engineering questions do not.
|
|
8
|
+
|
|
9
|
+
A reliable hook has explicit input, authority, output, and failure behavior.
|
|
10
|
+
|
|
11
|
+
## Hook contract
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
name:
|
|
15
|
+
event:
|
|
16
|
+
scope:
|
|
17
|
+
input refs:
|
|
18
|
+
handler:
|
|
19
|
+
reads:
|
|
20
|
+
writes/side effects:
|
|
21
|
+
credential(s) + scopes:
|
|
22
|
+
timeout:
|
|
23
|
+
retry/backoff:
|
|
24
|
+
idempotency key:
|
|
25
|
+
output/receipt:
|
|
26
|
+
on failure:
|
|
27
|
+
owner:
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Input
|
|
31
|
+
|
|
32
|
+
Prefer immutable or content-addressed references where possible: Git SHA, Merkle root,
|
|
33
|
+
deployment ID, schema version, artifact digest, event ID. Do not make the handler infer
|
|
34
|
+
which candidate it should operate on from mutable ambient state.
|
|
35
|
+
|
|
36
|
+
### Authority
|
|
37
|
+
|
|
38
|
+
Give the hook only the credential scopes it needs. A test job should not hold a
|
|
39
|
+
production deploy token. A postdeploy health checker should not need database-admin
|
|
40
|
+
credentials just to call a public health path.
|
|
41
|
+
|
|
42
|
+
### Output
|
|
43
|
+
|
|
44
|
+
Emit bounded structured evidence: status, timing, affected resource IDs, hashes,
|
|
45
|
+
versions, and sanitized error categories. Never emit raw secrets, access tokens, full
|
|
46
|
+
request headers, or unbounded logs into a receipt.
|
|
47
|
+
|
|
48
|
+
### Failure
|
|
49
|
+
|
|
50
|
+
Choose deliberately:
|
|
51
|
+
|
|
52
|
+
- `warn`: record but do not block;
|
|
53
|
+
- `block`: candidate cannot advance;
|
|
54
|
+
- `rollback`: revert/disable when rollback is proven safe;
|
|
55
|
+
- `quarantine`: keep the candidate deployed but unavailable while investigation runs.
|
|
56
|
+
|
|
57
|
+
Do not default every hook to "retry forever." Retries need a bounded count/window and
|
|
58
|
+
idempotency semantics for side effects.
|
|
59
|
+
|
|
60
|
+
## Ingress versus egress hooks
|
|
61
|
+
|
|
62
|
+
**Ingress** receives something from outside: HTTP request, webhook, auth callback,
|
|
63
|
+
queue event. Validate identity/signature/schema before trusted business logic.
|
|
64
|
+
|
|
65
|
+
**Egress** sends something outward: API call, deploy, notification, database write.
|
|
66
|
+
Validate destination identity/allowlist, credential scope, request contract, timeout,
|
|
67
|
+
and retry/idempotency behavior.
|
|
68
|
+
|
|
69
|
+
This makes operational I/O inspectable as edges in the same graph as code imports.
|
|
70
|
+
|
|
71
|
+
## Deploy hook composition
|
|
72
|
+
|
|
73
|
+
A robust deploy wrapper behaves like:
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
predeploy
|
|
77
|
+
inspect/security/build/tests
|
|
78
|
+
capture evidence
|
|
79
|
+
|
|
80
|
+
provider deploy
|
|
81
|
+
capture deployment/version ID
|
|
82
|
+
|
|
83
|
+
postdeploy
|
|
84
|
+
smoke critical routes/contracts
|
|
85
|
+
inspect metrics if available
|
|
86
|
+
|
|
87
|
+
promote
|
|
88
|
+
success receipt only if postdeploy is green
|
|
89
|
+
|
|
90
|
+
failure
|
|
91
|
+
failure receipt
|
|
92
|
+
rollback/disable/quarantine according to project policy
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Do not hide this behind one opaque shell string when individual steps need different
|
|
96
|
+
failure semantics or receipts.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"skills": [
|
|
4
|
+
{
|
|
5
|
+
"id": "agentsam-jr-dev",
|
|
6
|
+
"aliases": [
|
|
7
|
+
"agentsam_jr_dev",
|
|
8
|
+
"jr-dev"
|
|
9
|
+
],
|
|
10
|
+
"title": "AgentSam Jr Dev",
|
|
11
|
+
"description": "Teach, inspect, build, and revise real software using beginner-friendly language grounded in repository and runtime evidence.",
|
|
12
|
+
"entry": "agentsam-jr-dev/SKILL.md",
|
|
13
|
+
"references": [
|
|
14
|
+
"agentsam-jr-dev/references/web-application-fundamentals.md",
|
|
15
|
+
"agentsam-jr-dev/references/real-application-logic.md"
|
|
16
|
+
],
|
|
17
|
+
"user_invocable": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "agentsam-app-fundamentals",
|
|
21
|
+
"aliases": [
|
|
22
|
+
"app-fundamentals",
|
|
23
|
+
"application-fundamentals",
|
|
24
|
+
"quick-bytes"
|
|
25
|
+
],
|
|
26
|
+
"title": "AgentSam Application Fundamentals",
|
|
27
|
+
"description": "Build and review applications using explicit execution ownership, contracts, credentialed destinations, dependency graphs, AST evidence, and Merkle evidence.",
|
|
28
|
+
"entry": "agentsam-app-fundamentals/SKILL.md",
|
|
29
|
+
"references": [
|
|
30
|
+
"agentsam-app-fundamentals/references/trust-credentials-and-destinations.md",
|
|
31
|
+
"agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md"
|
|
32
|
+
],
|
|
33
|
+
"user_invocable": true
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "agentsam-progression-guard",
|
|
37
|
+
"aliases": [
|
|
38
|
+
"progression-guard",
|
|
39
|
+
"no-regress",
|
|
40
|
+
"ship-guard",
|
|
41
|
+
"release-guard"
|
|
42
|
+
],
|
|
43
|
+
"title": "AgentSam Progression Guard",
|
|
44
|
+
"description": "Prevent silent regression with explicit local, CI, predeploy, deploy, postdeploy, promotion, and runtime evidence gates.",
|
|
45
|
+
"entry": "agentsam-progression-guard/SKILL.md",
|
|
46
|
+
"references": [
|
|
47
|
+
"agentsam-progression-guard/references/checkpoint-chain.md",
|
|
48
|
+
"agentsam-progression-guard/references/hooks-operational-io.md"
|
|
49
|
+
],
|
|
50
|
+
"user_invocable": true
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
@@ -5,3 +5,10 @@ export {
|
|
|
5
5
|
listCapabilities,
|
|
6
6
|
} from './manifest.js';
|
|
7
7
|
export { repositorySnapshot } from './repository-snapshot.js';
|
|
8
|
+
export {
|
|
9
|
+
REPOSITORY_SNAPSHOT_VIEWS,
|
|
10
|
+
buildRepositorySnapshotFacets,
|
|
11
|
+
normalizeRepositorySnapshotFilters,
|
|
12
|
+
projectRepositorySnapshot,
|
|
13
|
+
repositoryFileMatches,
|
|
14
|
+
} from './repository-snapshot-view.js';
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const VIEW_NAMES = new Set(['index', 'files', 'full']);
|
|
4
|
+
const FILTER_FIELDS = Object.freeze([
|
|
5
|
+
'system', 'package', 'category', 'layer', 'kind', 'language', 'role', 'execution_domain', 'tag', 'path', 'symbol', 'import', 'match',
|
|
6
|
+
]);
|
|
7
|
+
const DEFAULT_LIMIT = 50;
|
|
8
|
+
const MAX_LIMIT = 500;
|
|
9
|
+
const DEFAULT_FACET_LIMIT = 48;
|
|
10
|
+
const MAX_FACET_LIMIT = 200;
|
|
11
|
+
|
|
12
|
+
function clean(value) {
|
|
13
|
+
return value == null ? '' : String(value).trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function sha256(value) {
|
|
17
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function values(input) {
|
|
21
|
+
if (input == null) return [];
|
|
22
|
+
const raw = Array.isArray(input) ? input : [input];
|
|
23
|
+
return [...new Set(raw.map(clean).filter(Boolean))];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function boundedInt(value, fallback, max) {
|
|
27
|
+
const number = Number(value);
|
|
28
|
+
if (!Number.isInteger(number) || number < 1) return fallback;
|
|
29
|
+
return Math.min(max, number);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function globRegex(glob) {
|
|
33
|
+
const input = clean(glob).replace(/^\.\//, '');
|
|
34
|
+
let source = '^';
|
|
35
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
36
|
+
const char = input[i];
|
|
37
|
+
if (char === '*') {
|
|
38
|
+
if (input[i + 1] === '*') { source += '.*'; i += 1; }
|
|
39
|
+
else source += '[^/]*';
|
|
40
|
+
} else if (char === '?') source += '[^/]';
|
|
41
|
+
else source += /[\\^$.*+?()[\]{}|]/.test(char) ? `\\${char}` : char;
|
|
42
|
+
}
|
|
43
|
+
return new RegExp(`${source}$`, 'i');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function pathMatches(path, selectors) {
|
|
47
|
+
if (!selectors.length) return true;
|
|
48
|
+
const candidate = clean(path).replace(/^\.\//, '');
|
|
49
|
+
return selectors.some((selector) => {
|
|
50
|
+
const normalized = selector.replace(/^\.\//, '');
|
|
51
|
+
if (normalized.includes('*') || normalized.includes('?')) return globRegex(normalized).test(candidate);
|
|
52
|
+
return candidate === normalized || candidate.startsWith(`${normalized.replace(/\/$/, '')}/`);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function scalarMatches(value, selectors) {
|
|
57
|
+
if (!selectors.length) return true;
|
|
58
|
+
const candidate = clean(value).toLowerCase();
|
|
59
|
+
return selectors.some((selector) => candidate === selector.toLowerCase());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function listMatches(list, selectors) {
|
|
63
|
+
if (!selectors.length) return true;
|
|
64
|
+
const set = new Set((Array.isArray(list) ? list : []).map((value) => clean(value).toLowerCase()));
|
|
65
|
+
return selectors.some((selector) => set.has(selector.toLowerCase()));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function lexicalMatches(entry, terms) {
|
|
69
|
+
if (!terms.length) return true;
|
|
70
|
+
const haystack = [
|
|
71
|
+
entry.path, entry.system, entry.package, entry.category, entry.layer, entry.kind, entry.language, entry.role, entry.execution_domain,
|
|
72
|
+
...(entry.tags || []), ...(entry.symbols || []), ...(entry.imports || []),
|
|
73
|
+
].map(clean).join('\n').toLowerCase();
|
|
74
|
+
return terms.every((term) => haystack.includes(term.toLowerCase()));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function normalizeRepositorySnapshotFilters(input = {}) {
|
|
78
|
+
return Object.fromEntries(FILTER_FIELDS.map((field) => [field, values(input[field])]).filter(([, list]) => list.length));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function repositoryFileMatches(entry, filters = {}) {
|
|
82
|
+
const f = normalizeRepositorySnapshotFilters(filters);
|
|
83
|
+
return scalarMatches(entry.system, f.system || [])
|
|
84
|
+
&& scalarMatches(entry.package, f.package || [])
|
|
85
|
+
&& scalarMatches(entry.category, f.category || [])
|
|
86
|
+
&& scalarMatches(entry.layer, f.layer || [])
|
|
87
|
+
&& scalarMatches(entry.kind, f.kind || [])
|
|
88
|
+
&& scalarMatches(entry.language, f.language || [])
|
|
89
|
+
&& scalarMatches(entry.role, f.role || [])
|
|
90
|
+
&& scalarMatches(entry.execution_domain, f.execution_domain || [])
|
|
91
|
+
&& listMatches(entry.tags, f.tag || [])
|
|
92
|
+
&& pathMatches(entry.path, f.path || [])
|
|
93
|
+
&& listMatches(entry.symbols, f.symbol || [])
|
|
94
|
+
&& listMatches(entry.imports, f.import || [])
|
|
95
|
+
&& lexicalMatches(entry, f.match || []);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function countFacet(entries, getter, limit) {
|
|
99
|
+
const counts = new Map();
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
for (const raw of getter(entry)) {
|
|
102
|
+
const value = clean(raw);
|
|
103
|
+
if (!value) continue;
|
|
104
|
+
counts.set(value, (counts.get(value) || 0) + 1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return [...counts.entries()]
|
|
108
|
+
.map(([value, count]) => ({ value, count }))
|
|
109
|
+
.sort((a, b) => b.count - a.count || a.value.localeCompare(b.value))
|
|
110
|
+
.slice(0, limit);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function buildRepositorySnapshotFacets(entries = [], { limit = DEFAULT_FACET_LIMIT } = {}) {
|
|
114
|
+
const bounded = boundedInt(limit, DEFAULT_FACET_LIMIT, MAX_FACET_LIMIT);
|
|
115
|
+
const scalar = (field) => (entry) => entry[field] == null ? [] : [entry[field]];
|
|
116
|
+
return {
|
|
117
|
+
systems: countFacet(entries, scalar('system'), bounded),
|
|
118
|
+
packages: countFacet(entries, scalar('package'), bounded),
|
|
119
|
+
layers: countFacet(entries, scalar('layer'), bounded),
|
|
120
|
+
roles: countFacet(entries, scalar('role'), bounded),
|
|
121
|
+
execution_domains: countFacet(entries, scalar('execution_domain'), bounded),
|
|
122
|
+
kinds: countFacet(entries, scalar('kind'), bounded),
|
|
123
|
+
languages: countFacet(entries, scalar('language'), bounded),
|
|
124
|
+
tags: countFacet(entries, (entry) => entry.tags || [], bounded),
|
|
125
|
+
categories: countFacet(entries, scalar('category'), bounded),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function compactEntry(entry) {
|
|
130
|
+
return {
|
|
131
|
+
path: entry.path,
|
|
132
|
+
...(entry.package ? { package: entry.package } : {}),
|
|
133
|
+
...(entry.package_root ? { package_root: entry.package_root } : {}),
|
|
134
|
+
...(entry.system ? { system: entry.system } : {}),
|
|
135
|
+
...(entry.category ? { category: entry.category } : {}),
|
|
136
|
+
...(entry.layer ? { layer: entry.layer } : {}),
|
|
137
|
+
...(entry.kind ? { kind: entry.kind } : {}),
|
|
138
|
+
...(entry.language ? { language: entry.language } : {}),
|
|
139
|
+
...(entry.role ? { role: entry.role } : {}),
|
|
140
|
+
...(entry.execution_domain ? { execution_domain: entry.execution_domain } : {}),
|
|
141
|
+
...(entry.tags?.length ? { tags: entry.tags } : {}),
|
|
142
|
+
...(entry.symbols?.length ? { symbols: entry.symbols } : {}),
|
|
143
|
+
...(entry.imports?.length ? { imports: entry.imports } : {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function compactTrustBoundary(analysis, limit = 20) {
|
|
148
|
+
const source = analysis?.trust_boundary;
|
|
149
|
+
if (!source) return undefined;
|
|
150
|
+
const findings = Array.isArray(source.findings) ? source.findings : [];
|
|
151
|
+
return {
|
|
152
|
+
trust_boundary: {
|
|
153
|
+
schema_version: source.schema_version,
|
|
154
|
+
analyzer: source.analyzer,
|
|
155
|
+
metadata_root: source.metadata_root,
|
|
156
|
+
complete: source.complete,
|
|
157
|
+
status: source.status,
|
|
158
|
+
ok: source.ok,
|
|
159
|
+
browser_roots: source.browser_roots,
|
|
160
|
+
browser_reachable_files: source.browser_reachable_files,
|
|
161
|
+
ast_files: source.ast_files,
|
|
162
|
+
finding_count: findings.length,
|
|
163
|
+
findings_returned: Math.min(findings.length, limit),
|
|
164
|
+
findings_truncated: findings.length > limit,
|
|
165
|
+
findings: findings.slice(0, limit).map((item) => ({
|
|
166
|
+
kind: item.kind,
|
|
167
|
+
severity: item.severity,
|
|
168
|
+
source: item.source,
|
|
169
|
+
...(item.target ? { target: item.target } : {}),
|
|
170
|
+
...(item.specifier ? { specifier: item.specifier } : {}),
|
|
171
|
+
...(item.env ? { env: item.env } : {}),
|
|
172
|
+
...(item.repair?.action ? { repair_action: item.repair.action } : {}),
|
|
173
|
+
})),
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function projectionBase(snapshot) {
|
|
179
|
+
return {
|
|
180
|
+
schema_version: snapshot.schema_version,
|
|
181
|
+
capability: snapshot.capability,
|
|
182
|
+
snapshot_id: snapshot.snapshot_id,
|
|
183
|
+
created_at: snapshot.created_at,
|
|
184
|
+
content_hash: snapshot.content_hash,
|
|
185
|
+
repository: snapshot.repository,
|
|
186
|
+
tree: {
|
|
187
|
+
merkle_root: snapshot.tree?.merkle_root || null,
|
|
188
|
+
metadata_root: snapshot.tree?.metadata_root || null,
|
|
189
|
+
manifest: snapshot.tree?.manifest || null,
|
|
190
|
+
classifier: snapshot.tree?.classifier || null,
|
|
191
|
+
stats: snapshot.tree?.stats || null,
|
|
192
|
+
semantic_stats: snapshot.tree?.semantic_stats || null,
|
|
193
|
+
},
|
|
194
|
+
...(compactTrustBoundary(snapshot.analysis) ? { analysis: compactTrustBoundary(snapshot.analysis) } : {}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Produce a bounded, deterministic view over a canonical repository.snapshot.
|
|
200
|
+
* The canonical content_hash / Merkle identities are carried through unchanged;
|
|
201
|
+
* projection_hash identifies only this derived view and is never an authority root.
|
|
202
|
+
*/
|
|
203
|
+
export function projectRepositorySnapshot(snapshot, options = {}) {
|
|
204
|
+
if (!snapshot || snapshot.capability !== 'repository.snapshot' || !snapshot.tree) {
|
|
205
|
+
throw new Error('repository_snapshot_required');
|
|
206
|
+
}
|
|
207
|
+
const view = clean(options.view || 'index').toLowerCase();
|
|
208
|
+
if (!VIEW_NAMES.has(view)) throw new Error(`repository_snapshot_view_invalid:${view}`);
|
|
209
|
+
if (view === 'full') return snapshot;
|
|
210
|
+
|
|
211
|
+
const files = Array.isArray(snapshot.tree.files) ? snapshot.tree.files : [];
|
|
212
|
+
const filters = normalizeRepositorySnapshotFilters(options.filters || options);
|
|
213
|
+
const filtered = files.filter((entry) => repositoryFileMatches(entry, filters));
|
|
214
|
+
const limit = boundedInt(options.limit, DEFAULT_LIMIT, MAX_LIMIT);
|
|
215
|
+
const facetLimit = boundedInt(options.facetLimit ?? options.facet_limit, DEFAULT_FACET_LIMIT, MAX_FACET_LIMIT);
|
|
216
|
+
const base = projectionBase(snapshot);
|
|
217
|
+
const result = {
|
|
218
|
+
...base,
|
|
219
|
+
projection: {
|
|
220
|
+
format: 'agentsam-repository-view',
|
|
221
|
+
version: 1,
|
|
222
|
+
view,
|
|
223
|
+
source_snapshot_id: snapshot.snapshot_id,
|
|
224
|
+
source_content_hash: snapshot.content_hash,
|
|
225
|
+
filters,
|
|
226
|
+
matched: filtered.length,
|
|
227
|
+
returned: view === 'files' ? Math.min(filtered.length, limit) : 0,
|
|
228
|
+
truncated: view === 'files' && filtered.length > limit,
|
|
229
|
+
limit: view === 'files' ? limit : 0,
|
|
230
|
+
},
|
|
231
|
+
facets: buildRepositorySnapshotFacets(filtered, { limit: facetLimit }),
|
|
232
|
+
...(view === 'files' ? { files: filtered.slice(0, limit).map(compactEntry) } : {}),
|
|
233
|
+
};
|
|
234
|
+
result.projection.projection_hash = sha256({ ...result, projection: { ...result.projection, projection_hash: undefined } });
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const REPOSITORY_SNAPSHOT_VIEWS = Object.freeze([...VIEW_NAMES]);
|
|
@@ -5,7 +5,10 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { resolveGitContext } from '../lib/git-context.js';
|
|
8
|
+
import { getRepositoryId, tryReadProjectConfig } from '../lib/project-config.js';
|
|
8
9
|
import { buildMerkleTree } from '../lib/merkle/index.js';
|
|
10
|
+
import { gitIgnoredPaths } from '../lib/merkle/git-ignore.js';
|
|
11
|
+
import { analyzeExecutionBoundaries } from '../indexing/execution-boundary.js';
|
|
9
12
|
import { showLatestDeployReceipt } from '../lib/deploy-receipt/index.js';
|
|
10
13
|
import { CONFIG_PATH, canonical, readConfig, scopeKey } from '../knowledge/config.js';
|
|
11
14
|
import { openSqliteStore } from '../knowledge/stores/sqlite.js';
|
|
@@ -24,16 +27,6 @@ function providerForHost(host) {
|
|
|
24
27
|
return value ? 'git' : null;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
async function gitIgnoredPaths(root) {
|
|
28
|
-
const result = await execute('git', ['status', '--porcelain=v1', '--ignored=matching'], {
|
|
29
|
-
cwd: root,
|
|
30
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
31
|
-
});
|
|
32
|
-
return [...new Set(result.stdout.split('\n')
|
|
33
|
-
.filter((line) => line.startsWith('!! '))
|
|
34
|
-
.map((line) => line.slice(3).trim().replace(/\/$/, ''))
|
|
35
|
-
.filter((value) => value && !value.includes('\\') && !value.split('/').includes('..')))];
|
|
36
|
-
}
|
|
37
30
|
|
|
38
31
|
async function runRepositoryIntelligence(root, churnDays) {
|
|
39
32
|
const pythonRoot = fileURLToPath(new URL('../../python', import.meta.url));
|
|
@@ -121,21 +114,29 @@ export async function repositorySnapshot({ cwd = process.cwd(), churnDays = 30 }
|
|
|
121
114
|
const git = resolveGitContext({ cwd });
|
|
122
115
|
const root = git.root;
|
|
123
116
|
const ignored = await gitIgnoredPaths(root);
|
|
117
|
+
const projectConfig = tryReadProjectConfig(root);
|
|
124
118
|
const [intelligence, merkle, knowledge, deployReceipt] = await Promise.all([
|
|
125
119
|
runRepositoryIntelligence(root, churnDays),
|
|
126
|
-
buildMerkleTree(root, { exclude: ignored }),
|
|
120
|
+
buildMerkleTree(root, { exclude: ignored, semantic: true }),
|
|
127
121
|
readKnowledgeState(root),
|
|
128
122
|
showLatestDeployReceipt({ root }),
|
|
129
123
|
]);
|
|
130
124
|
const provider = providerForHost(git.remoteHost);
|
|
131
|
-
const portableRepositoryId = provider && git.repoFullName
|
|
132
|
-
|
|
125
|
+
const portableRepositoryId = provider && git.repoFullName
|
|
126
|
+
? `${provider}:${provider === 'github' ? git.repoFullName.toLowerCase() : git.repoFullName}`
|
|
127
|
+
: null;
|
|
128
|
+
const projectRepositoryId = getRepositoryId(projectConfig);
|
|
129
|
+
if (projectRepositoryId && knowledge.repository_id && projectRepositoryId !== knowledge.repository_id) {
|
|
130
|
+
throw new Error(`repository_identity_mismatch: project=${projectRepositoryId} knowledge=${knowledge.repository_id}`);
|
|
131
|
+
}
|
|
132
|
+
const repositoryId = projectRepositoryId || knowledge.repository_id || portableRepositoryId;
|
|
133
133
|
const packages = readPackageInventory(root, intelligence.manifests || []);
|
|
134
|
+
const trustBoundary = analyzeExecutionBoundaries(merkle.semantic);
|
|
134
135
|
|
|
135
136
|
const evidence = {
|
|
136
137
|
repository: {
|
|
137
138
|
repository_id: repositoryId,
|
|
138
|
-
identity_source: knowledge.repository_id ? 'knowledge-config' : portableRepositoryId ? 'git-remote' : 'unresolved',
|
|
139
|
+
identity_source: projectRepositoryId ? 'project-config' : knowledge.repository_id ? 'knowledge-config' : portableRepositoryId ? 'git-remote' : 'unresolved',
|
|
139
140
|
provider,
|
|
140
141
|
full_name: git.repoFullName,
|
|
141
142
|
remote_url: git.remoteUrl || null,
|
|
@@ -145,10 +146,20 @@ export async function repositorySnapshot({ cwd = process.cwd(), churnDays = 30 }
|
|
|
145
146
|
},
|
|
146
147
|
tree: {
|
|
147
148
|
merkle_root: merkle.rootHash,
|
|
149
|
+
metadata_root: merkle.semantic.rootHash,
|
|
150
|
+
manifest: {
|
|
151
|
+
format: merkle.format,
|
|
152
|
+
version: merkle.version,
|
|
153
|
+
hash_algorithm: merkle.algorithm,
|
|
154
|
+
policy_hash: merkle.policyHash,
|
|
155
|
+
},
|
|
156
|
+
classifier: merkle.semantic.classifier,
|
|
148
157
|
stats: merkle.stats,
|
|
158
|
+
semantic_stats: merkle.semantic.stats,
|
|
149
159
|
paths: merkle.entries
|
|
150
160
|
.filter((entry) => entry.type === 'file' || entry.type === 'symlink')
|
|
151
161
|
.map((entry) => entry.path),
|
|
162
|
+
files: merkle.semantic.entries,
|
|
152
163
|
},
|
|
153
164
|
intelligence: {
|
|
154
165
|
summary: intelligence.summary,
|
|
@@ -159,6 +170,9 @@ export async function repositorySnapshot({ cwd = process.cwd(), churnDays = 30 }
|
|
|
159
170
|
},
|
|
160
171
|
packages,
|
|
161
172
|
knowledge,
|
|
173
|
+
analysis: {
|
|
174
|
+
trust_boundary: trustBoundary,
|
|
175
|
+
},
|
|
162
176
|
deploy: deployReceipt ? {
|
|
163
177
|
status: deployReceipt.status,
|
|
164
178
|
root_hash: deployReceipt.root_hash,
|