motionloom 2.0.0 → 2.1.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/CHANGELOG.md +41 -0
- package/CODE_OF_CONDUCT.md +19 -0
- package/CONTRIBUTING.md +65 -0
- package/README.md +187 -134
- package/ROADMAP.md +32 -0
- package/SECURITY.md +27 -0
- package/SKILL.md +33 -8
- package/SUPPORT.md +23 -0
- package/agent-card.json +21 -6
- package/bin/motionloom.mjs +23 -5
- package/docs/STATUS.md +33 -0
- package/docs/audits/2.1.0-deep-stress-evaluation.md +97 -0
- package/docs/audits/data/2.1.0-deep-stress-6900.json +329 -0
- package/docs/audits/data/deep-stress-latest.json +329 -0
- package/docs/audits/external-project-corpus-2026-08-13.md +26 -0
- package/docs/releases/2.1.0.md +23 -0
- package/docs/releases/npm-publish-from-workstation.md +6 -6
- package/package.json +52 -26
- package/references/intelligence-core.md +1 -1
- package/schemas/project-memory.schema.json +180 -0
- package/scripts/analyze.py +56 -0
- package/scripts/capture-runtime-telemetry.py +119 -0
- package/scripts/devlab.py +126 -0
- package/scripts/docs-audit.py +96 -0
- package/scripts/eval-intelligence.py +23 -0
- package/scripts/eval-projects.py +156 -0
- package/scripts/intelligence.py +106 -6
- package/scripts/pr.py +150 -0
- package/scripts/prepack-clean.mjs +37 -0
- package/scripts/project-memory.py +483 -0
- package/scripts/project_memory_loader.py +31 -0
- package/scripts/release-verify.py +52 -0
- package/scripts/render.py +65 -0
- package/scripts/report.py +44 -2
- package/scripts/review-hook.py +13 -2
- package/scripts/skill-doctor.py +12 -2
- package/scripts/to-dotlottie.mjs +26 -20
- package/src/core/analyzer.py +174 -25
- package/tests/evals/intelligence-cases.json +10 -0
- package/tests/evals/project-corpus.json +51 -0
- package/tests/scripts/run_tests.py +52 -1
- package/tests/scripts/test_project_memory.py +129 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
MotionLoom 2.1.0 adds durable, relocatable **Project Memory** and a cross-platform execution layer for Ubuntu, macOS and Windows. The memory restores project identity, motion decisions, rejected patterns, remediation outcomes, runtime policy and freshness state after long periods without animation work or after an Agent context reset. It remains project context rather than approval: stale, invalid or cross-project memory is never treated as permission to generate, confirm or open a PR.
|
|
4
|
+
|
|
5
|
+
## Added
|
|
6
|
+
|
|
7
|
+
The release adds `schemas/project-memory.schema.json`, the `memory init`, `inspect`, `validate`, `refresh`, `recover`, `record-decision` and `record-outcome` lifecycle, atomic writes, canonical integrity checking, stable exit codes and a loader API for other MotionLoom scripts. Durable outcomes require `--user-confirmed`; unreviewed outcomes cannot become learning signals. Project identity binds to a normalized Git remote when available and falls back to the package/project name, so the memory is not tied to an absolute checkout path.
|
|
8
|
+
|
|
9
|
+
The integrity contract treats `project.root_path` as runtime metadata. A relocated checkout can be rebound to its current path during recovery without invalidating the durable payload, while meaningful direct edits still fail the integrity guard. Recovery also refreshes the current repository and package metadata atomically. Cross-project recovery remains fail-closed through identity comparison.
|
|
10
|
+
|
|
11
|
+
The npm CLI now routes memory, analyze, Dev Lab, render, PR preflight and runtime telemetry commands through platform-neutral Node/Python entrypoints. The implementation avoids Bash-only orchestration, hard-coded temporary paths, shell-string execution and system `zip`/`unzip` requirements in the user-facing workflow. The CI workflow includes an Ubuntu/macOS/Windows matrix for the cross-platform contract.
|
|
12
|
+
|
|
13
|
+
## Verified behavior
|
|
14
|
+
|
|
15
|
+
The release acceptance suite passes Python compilation, Project Memory recovery/relocation contracts, the full regression harness, intelligence evaluation, Skill Doctor, report contract, strict quality gate with browser review/intelligence/P1/benchmark/telemetry/attestation requirements, npm packaging dry-run and the Animation Dev Lab production build. The relocation fixture covers Unicode and space-containing paths, Git remote identity, user-confirmed decisions/outcomes, stale context detection and current-path rebinding.
|
|
16
|
+
|
|
17
|
+
`npm publish --dry-run --access public` produces the intended public tarball `motionloom-2.1.0.tgz` with package version `2.1.0`. Actual publication must be performed from the maintainer workstation using the authenticated npm account; this sandbox run does not publish the package.
|
|
18
|
+
|
|
19
|
+
## Trust boundary and remaining scope
|
|
20
|
+
|
|
21
|
+
Project Memory is a continuity and context mechanism, not a user-review substitute. It does not approve animation quality, visual fidelity, accessibility, source licensing, runtime evidence or PR side effects. Dev Lab review, artifact binding, attestation verification and explicit user confirmation remain required before any PR operation.
|
|
22
|
+
|
|
23
|
+
Visual comparison with provenance-labeled fixtures, append-only benchmark history, multi-project/browser/device corpus and remediation metrics for first-pass acceptance and rerender avoidance remain the next roadmap slice. The Dev Lab build still reports a bundle-size advisory for the main JavaScript chunk; it is a performance warning, not an acceptance failure.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Publish MotionLoom to npm from your workstation
|
|
2
2
|
|
|
3
|
-
This guide publishes the prepared `motionloom@2.
|
|
3
|
+
This guide publishes the prepared `motionloom@2.1.0` package from your own computer. The npm password, authenticator code and access token must stay on your computer; never paste them into chat, GitHub issues or repository files.
|
|
4
4
|
|
|
5
5
|
## 1. Install prerequisites
|
|
6
6
|
|
|
@@ -48,7 +48,7 @@ npm pack --dry-run --json --ignore-scripts
|
|
|
48
48
|
npm publish --dry-run --access public
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
The dry-run should report `motionloom@2.
|
|
51
|
+
The dry-run should report `motionloom@2.1.0`, public access, and the current package file list. Do not hard-code a historical file count: inspect the JSON output and compare it with the checked-in package allowlist. The prepack hook removes generated Python bytecode before packaging. Do not publish if the dry-run shows private keys, `.env` files, `artifacts/`, `dev-lab/` or `__pycache__/` entries.
|
|
52
52
|
|
|
53
53
|
## 5. Publish the package
|
|
54
54
|
|
|
@@ -58,15 +58,15 @@ When the dry-run is correct and `npm whoami` shows the intended account, publish
|
|
|
58
58
|
npm publish --access public
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
The version `2.
|
|
61
|
+
The version `2.1.0` becomes immutable on npm after a successful publish. If npm reports that the version already exists, stop and verify the registry instead of trying to overwrite it.
|
|
62
62
|
|
|
63
63
|
## 6. Verify the registry publication
|
|
64
64
|
|
|
65
|
-
Run the following commands and confirm that they return `2.
|
|
65
|
+
Run the following commands and confirm that they return `2.1.0` and a tarball URL:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
68
|
npm view motionloom version --registry=https://registry.npmjs.org/
|
|
69
|
-
npm view motionloom@2.
|
|
69
|
+
npm view motionloom@2.1.0 name version license dist.tarball dist.shasum --json \
|
|
70
70
|
--registry=https://registry.npmjs.org/
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -76,7 +76,7 @@ Then test installation in a clean temporary directory:
|
|
|
76
76
|
TMP_DIR="$(mktemp -d)"
|
|
77
77
|
cd "$TMP_DIR"
|
|
78
78
|
npm init --yes
|
|
79
|
-
npm install motionloom@2.
|
|
79
|
+
npm install motionloom@2.1.0
|
|
80
80
|
motionloom --help
|
|
81
81
|
motionloom doctor
|
|
82
82
|
```
|
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "motionloom",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"packageManager": "pnpm@11.20.0",
|
|
5
6
|
"description": "MotionLoom: project-aware animation Skill with graph, provenance, Motion IR, runtime telemetry, DSSE-compatible signed attestation, external evidence verification and Dev Lab review.",
|
|
7
|
+
"author": "lenhonbp",
|
|
6
8
|
"bin": {
|
|
7
9
|
"motionloom": "bin/motionloom.mjs"
|
|
8
10
|
},
|
|
9
11
|
"files": [
|
|
10
12
|
"README.md",
|
|
13
|
+
"CHANGELOG.md",
|
|
14
|
+
"ROADMAP.md",
|
|
15
|
+
"CONTRIBUTING.md",
|
|
16
|
+
"CODE_OF_CONDUCT.md",
|
|
17
|
+
"SECURITY.md",
|
|
18
|
+
"SUPPORT.md",
|
|
11
19
|
"SKILL.md",
|
|
12
20
|
"LICENSE",
|
|
13
21
|
"agent-card.json",
|
|
@@ -23,7 +31,8 @@
|
|
|
23
31
|
"project-context.example.json",
|
|
24
32
|
"tests/evals",
|
|
25
33
|
"tests/scripts/run_tests.py",
|
|
26
|
-
"tests/scripts/test_attestation.py"
|
|
34
|
+
"tests/scripts/test_attestation.py",
|
|
35
|
+
"tests/scripts/test_project_memory.py"
|
|
27
36
|
],
|
|
28
37
|
"keywords": [
|
|
29
38
|
"animation",
|
|
@@ -35,7 +44,13 @@
|
|
|
35
44
|
"gsap",
|
|
36
45
|
"framer-motion",
|
|
37
46
|
"runtime-evidence",
|
|
38
|
-
"developer-tools"
|
|
47
|
+
"developer-tools",
|
|
48
|
+
"project-memory",
|
|
49
|
+
"motion-ir",
|
|
50
|
+
"provenance",
|
|
51
|
+
"attestation",
|
|
52
|
+
"browser-review",
|
|
53
|
+
"dev-lab"
|
|
39
54
|
],
|
|
40
55
|
"repository": {
|
|
41
56
|
"type": "git",
|
|
@@ -53,35 +68,46 @@
|
|
|
53
68
|
"python": ">=3.11"
|
|
54
69
|
},
|
|
55
70
|
"scripts": {
|
|
56
|
-
"prepack": "
|
|
57
|
-
"test": "
|
|
58
|
-
"validate": "
|
|
59
|
-
"doctor": "
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
71
|
+
"prepack": "node scripts/prepack-clean.mjs",
|
|
72
|
+
"test": "node bin/motionloom.mjs test",
|
|
73
|
+
"validate": "node bin/motionloom.mjs quality-gate --all",
|
|
74
|
+
"doctor": "node bin/motionloom.mjs doctor --json",
|
|
75
|
+
"docs:check": "python3 scripts/docs-audit.py",
|
|
76
|
+
"eval:projects": "python3 scripts/eval-projects.py --allow-insufficient",
|
|
77
|
+
"release:verify": "python3 scripts/release-verify.py",
|
|
78
|
+
"report": "node bin/motionloom.mjs report",
|
|
79
|
+
"report:check": "node bin/motionloom.mjs report-contract",
|
|
80
|
+
"review": "node bin/motionloom.mjs review-hook",
|
|
81
|
+
"devlab": "node bin/motionloom.mjs devlab",
|
|
82
|
+
"manifest": "node bin/motionloom.mjs manifest",
|
|
83
|
+
"intelligence": "node bin/motionloom.mjs intelligence",
|
|
84
|
+
"intelligence:registry": "node bin/motionloom.mjs intelligence capabilities build --output capability-registry.json",
|
|
85
|
+
"intelligence:registry:check": "node bin/motionloom.mjs intelligence capabilities validate --path capability-registry.json",
|
|
86
|
+
"intelligence:p1": "node bin/motionloom.mjs intelligence",
|
|
87
|
+
"quality:p1": "node bin/motionloom.mjs quality-gate --require-intelligence --require-p1 --require-benchmark",
|
|
88
|
+
"eval:intelligence": "node bin/motionloom.mjs eval-intelligence",
|
|
89
|
+
"audit:deep": "node bin/motionloom.mjs deep-audit --iterations 5000 --seed 20260813 --report docs/audits/data/deep-stress-latest.json",
|
|
90
|
+
"memory:init": "node bin/motionloom.mjs memory init --project-root .",
|
|
91
|
+
"memory:bootstrap": "node bin/motionloom.mjs analyze . --init-memory",
|
|
92
|
+
"memory:recover": "node bin/motionloom.mjs memory recover --project-root .",
|
|
93
|
+
"memory:validate": "node bin/motionloom.mjs memory validate --project-root . --json",
|
|
94
|
+
"memory:refresh": "node bin/motionloom.mjs memory refresh --project-root . --json",
|
|
95
|
+
"benchmark:semantic": "node bin/motionloom.mjs intelligence semantic-lint benchmark --task-dir artifacts/professional-review-e2e --iterations 25 --threshold-ms 500",
|
|
96
|
+
"evidence:verify": "node bin/motionloom.mjs evidence-verify --scene-dir src/output/browser-review-smoke --task-dir artifacts/browser-review-smoke-task --runtime-evidence runtime-adapters/runtime-evidence.json --max-age-days 1 --output artifacts/browser-review-smoke-task/evidence-verifier-report.json",
|
|
97
|
+
"attestation:build": "node bin/motionloom.mjs attestation build",
|
|
98
|
+
"attestation:verify": "node bin/motionloom.mjs verify-attestation",
|
|
99
|
+
"attestation:policy": "node bin/motionloom.mjs attestation validate-policy",
|
|
100
|
+
"attestation:keygen": "node bin/motionloom.mjs attestation-keygen",
|
|
101
|
+
"quality:telemetry": "node bin/motionloom.mjs quality-gate --scene browser-review-smoke --context artifacts/browser-review-smoke-task/project-context.json --task-dir artifacts/browser-review-smoke-task --require-browser-review --require-intelligence --require-p1 --require-benchmark --require-telemetry",
|
|
102
|
+
"quality:attestation": "node bin/motionloom.mjs quality-gate --scene browser-review-smoke --context artifacts/browser-review-smoke-task/project-context.json --task-dir artifacts/browser-review-smoke-task --require-browser-review --require-intelligence --require-p1 --require-benchmark --require-telemetry --require-attestation",
|
|
103
|
+
"pack:dotlottie": "node scripts/to-dotlottie.mjs",
|
|
79
104
|
"runtime:test": "node scripts/runtime-adapters.mjs"
|
|
80
105
|
},
|
|
81
106
|
"dependencies": {
|
|
82
107
|
"@lottiefiles/dotlottie-web": "^0.79.0",
|
|
83
108
|
"@napi-rs/canvas": "^1.0.5",
|
|
84
109
|
"@rive-app/canvas": "^2.39.2",
|
|
110
|
+
"fflate": "^0.8.3",
|
|
85
111
|
"framer-motion": "^13.1.0",
|
|
86
112
|
"gsap": "^3.15.0",
|
|
87
113
|
"react": "^19.2.8",
|
|
@@ -13,7 +13,7 @@ Use this reference when an Agent needs to understand or extend MotionLoom's task
|
|
|
13
13
|
| `replay-bundle.json` | `python3 scripts/intelligence.py replay capture --root <root> --task-dir <task>` | Captures clean-root artifact hashes and environment metadata; verification fails on mutation or omission. |
|
|
14
14
|
| `semantic-lint-report.json` | `python3 scripts/intelligence.py semantic-lint build --task-dir <task>` | Reports intent, timing, easing, accessibility, performance and anti-pattern findings with severity, confidence and evidence. |
|
|
15
15
|
| `semantic-lint-benchmark.json` | `python3 scripts/intelligence.py semantic-lint benchmark --task-dir <task>` | Measures in-process semantic-lint execution, rule coverage and p95 against a deterministic threshold; it is a performance contract, not visual approval. |
|
|
16
|
-
| `runtime-telemetry.json` | `
|
|
16
|
+
| `runtime-telemetry.json` | `motionloom runtime-telemetry <scene> artifacts/<task-id>` | Records deterministic scrub-point observations, RAF timing, runtime state hashes and source/manifest/Motion IR bindings from the real adapter harness. The npm CLI is the cross-platform entrypoint; the legacy `.sh` wrapper remains for Unix compatibility. |
|
|
17
17
|
| `evidence-verifier-report.json` | `python3 scripts/evidence-verifier.py --scene-dir <scene-dir> --task-dir <task> --runtime-evidence <path>` | Read-only external verification result for task/scene/path/hash/age integrity; `approval` is always `false`. |
|
|
18
18
|
| `attestation-statement.json` | `python3 scripts/attestation.py statement --scene-dir <scene-dir> --task-dir <task> --context <context>` | Derives canonical task/scene/source/manifest/Motion IR/evidence hash bindings before signing. |
|
|
19
19
|
| `attestation.json` | `python3 scripts/attestation.py build --statement <statement> --private-key <key> --key-id <id>` | DSSE-compatible Ed25519 envelope over the versioned statement; `approval` is always `false`. |
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://motionloom.dev/schemas/project-memory.schema.json",
|
|
4
|
+
"title": "MotionLoom Project Memory",
|
|
5
|
+
"description": "Durable, relocatable project memory for recovering animation decisions and context across Agent sessions.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"schema_version",
|
|
10
|
+
"memory_id",
|
|
11
|
+
"created_at",
|
|
12
|
+
"updated_at",
|
|
13
|
+
"project",
|
|
14
|
+
"context",
|
|
15
|
+
"motion_principles",
|
|
16
|
+
"policies",
|
|
17
|
+
"decisions",
|
|
18
|
+
"rejected_patterns",
|
|
19
|
+
"remediation",
|
|
20
|
+
"freshness",
|
|
21
|
+
"recovery"
|
|
22
|
+
],
|
|
23
|
+
"properties": {
|
|
24
|
+
"schema_version": { "const": "1.0" },
|
|
25
|
+
"memory_id": { "type": "string", "pattern": "^pm-[a-z0-9][a-z0-9._-]*$" },
|
|
26
|
+
"created_at": { "type": "string", "format": "date-time" },
|
|
27
|
+
"updated_at": { "type": "string", "format": "date-time" },
|
|
28
|
+
"project": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"required": ["project_id", "name", "root_path", "repository"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"project_id": { "type": "string", "minLength": 3 },
|
|
34
|
+
"name": { "type": "string", "minLength": 1 },
|
|
35
|
+
"root_path": { "type": "string", "minLength": 1 },
|
|
36
|
+
"repository": { "type": ["string", "null"] },
|
|
37
|
+
"package_name": { "type": ["string", "null"] }
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"context": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"additionalProperties": false,
|
|
43
|
+
"required": ["path", "sha256", "schema_version"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"path": { "type": "string", "minLength": 1 },
|
|
46
|
+
"sha256": { "type": ["string", "null"], "pattern": "^$|^[a-f0-9]{64}$" },
|
|
47
|
+
"schema_version": { "type": ["string", "null"] },
|
|
48
|
+
"generated_at": { "type": ["string", "null"] }
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"motion_principles": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"additionalProperties": false,
|
|
54
|
+
"required": ["duration_ms", "easing", "rhythm", "reduced_motion", "notes"],
|
|
55
|
+
"properties": {
|
|
56
|
+
"duration_ms": { "type": ["number", "null"], "minimum": 0 },
|
|
57
|
+
"easing": { "type": ["string", "null"] },
|
|
58
|
+
"rhythm": { "type": ["string", "null"] },
|
|
59
|
+
"reduced_motion": { "type": ["string", "null"] },
|
|
60
|
+
"notes": { "type": "array", "items": { "type": "string" } }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"policies": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"additionalProperties": false,
|
|
66
|
+
"required": ["asset", "runtime"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"asset": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"additionalProperties": false,
|
|
71
|
+
"required": ["authoritative_sources", "license_required", "unknown_asset_policy"],
|
|
72
|
+
"properties": {
|
|
73
|
+
"authoritative_sources": { "type": "array", "items": { "type": "string" } },
|
|
74
|
+
"license_required": { "type": "boolean" },
|
|
75
|
+
"unknown_asset_policy": { "type": "string", "enum": ["block", "review", "allow"] }
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"runtime": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"additionalProperties": false,
|
|
81
|
+
"required": ["preferred_frameworks", "verified_runtimes", "browser_matrix"],
|
|
82
|
+
"properties": {
|
|
83
|
+
"preferred_frameworks": { "type": "array", "items": { "type": "string" } },
|
|
84
|
+
"verified_runtimes": { "type": "array", "items": { "type": "string" } },
|
|
85
|
+
"browser_matrix": { "type": "array", "items": { "type": "string" } }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"decisions": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": { "$ref": "#/$defs/decision" }
|
|
93
|
+
},
|
|
94
|
+
"rejected_patterns": {
|
|
95
|
+
"type": "array",
|
|
96
|
+
"items": { "$ref": "#/$defs/rejectedPattern" }
|
|
97
|
+
},
|
|
98
|
+
"remediation": {
|
|
99
|
+
"type": "array",
|
|
100
|
+
"items": { "$ref": "#/$defs/remediation" }
|
|
101
|
+
},
|
|
102
|
+
"freshness": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"required": ["status", "checked_at", "context_hash", "project_fingerprint", "reason"],
|
|
106
|
+
"properties": {
|
|
107
|
+
"status": { "type": "string", "enum": ["fresh", "stale", "needs_context", "invalid"] },
|
|
108
|
+
"checked_at": { "type": "string", "format": "date-time" },
|
|
109
|
+
"context_hash": { "type": ["string", "null"], "pattern": "^$|^[a-f0-9]{64}$" },
|
|
110
|
+
"project_fingerprint": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
111
|
+
"reason": { "type": "string", "minLength": 1 }
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"recovery": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"additionalProperties": false,
|
|
117
|
+
"required": ["last_task_id", "next_actions"],
|
|
118
|
+
"properties": {
|
|
119
|
+
"last_task_id": { "type": ["string", "null"] },
|
|
120
|
+
"next_actions": { "type": "array", "items": { "type": "string" } },
|
|
121
|
+
"last_review_state": { "type": ["string", "null"] }
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"integrity": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"additionalProperties": false,
|
|
127
|
+
"required": ["canonical_sha256"],
|
|
128
|
+
"properties": {
|
|
129
|
+
"canonical_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"$defs": {
|
|
134
|
+
"decision": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"additionalProperties": false,
|
|
137
|
+
"required": ["id", "recorded_at", "status", "summary", "rationale", "user_confirmed", "source_task_id", "evidence"],
|
|
138
|
+
"properties": {
|
|
139
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
|
|
140
|
+
"recorded_at": { "type": "string", "format": "date-time" },
|
|
141
|
+
"status": { "type": "string", "enum": ["accepted", "rejected", "superseded"] },
|
|
142
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
143
|
+
"rationale": { "type": "string" },
|
|
144
|
+
"user_confirmed": { "type": "boolean" },
|
|
145
|
+
"source_task_id": { "type": ["string", "null"] },
|
|
146
|
+
"evidence": { "type": "array", "items": { "type": "string" } }
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
"rejectedPattern": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"additionalProperties": false,
|
|
152
|
+
"required": ["id", "recorded_at", "pattern", "reason", "source_task_id"],
|
|
153
|
+
"properties": {
|
|
154
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
|
|
155
|
+
"recorded_at": { "type": "string", "format": "date-time" },
|
|
156
|
+
"pattern": { "type": "string", "minLength": 1 },
|
|
157
|
+
"reason": { "type": "string" },
|
|
158
|
+
"source_task_id": { "type": ["string", "null"] }
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"remediation": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"additionalProperties": false,
|
|
164
|
+
"required": ["id", "recorded_at", "issue_id", "summary", "root_cause", "resolution", "result", "correction_count", "rerun_scope", "user_confirmed", "source_task_id"],
|
|
165
|
+
"properties": {
|
|
166
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
|
|
167
|
+
"recorded_at": { "type": "string", "format": "date-time" },
|
|
168
|
+
"issue_id": { "type": "string", "minLength": 1 },
|
|
169
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
170
|
+
"root_cause": { "type": "string" },
|
|
171
|
+
"resolution": { "type": "string" },
|
|
172
|
+
"result": { "type": "string", "enum": ["pass", "fail", "partial", "unknown"] },
|
|
173
|
+
"correction_count": { "type": "integer", "minimum": 0 },
|
|
174
|
+
"rerun_scope": { "type": "array", "items": { "type": "string" } },
|
|
175
|
+
"user_confirmed": { "type": "boolean" },
|
|
176
|
+
"source_task_id": { "type": ["string", "null"] }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cross-platform project analyzer entrypoint.
|
|
3
|
+
|
|
4
|
+
This replaces the npm CLI's Bash-only analyzer path while preserving the
|
|
5
|
+
existing analyzer implementation and refreshing Project Memory when present.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
16
|
+
sys.path.insert(0, str(ROOT))
|
|
17
|
+
|
|
18
|
+
from src.core.analyzer import analyze # noqa: E402
|
|
19
|
+
from project_memory_loader import refresh_if_present # noqa: E402
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> int:
|
|
23
|
+
parser = argparse.ArgumentParser(description="Analyze a host project and refresh MotionLoom Project Memory.")
|
|
24
|
+
parser.add_argument("project_root", nargs="?", default=".")
|
|
25
|
+
parser.add_argument("--output")
|
|
26
|
+
parser.add_argument("--init-memory", action="store_true", help="Create .motionloom/project-memory.json if it does not exist")
|
|
27
|
+
parser.add_argument("--max-files", type=int, default=2500, help="Maximum files to inspect")
|
|
28
|
+
parser.add_argument("--max-bytes", type=int, default=25_000_000, help="Maximum file bytes to inspect")
|
|
29
|
+
parser.add_argument("--max-seconds", type=float, default=10.0, help="Maximum traversal time")
|
|
30
|
+
parser.add_argument("--ignore-dir", action="append", default=[], help="Directory name to skip; repeatable")
|
|
31
|
+
parser.add_argument("--ignore-glob", action="append", default=[], help="Relative path glob to skip; repeatable")
|
|
32
|
+
args = parser.parse_args()
|
|
33
|
+
root = Path(args.project_root).expanduser().resolve()
|
|
34
|
+
if not root.is_dir():
|
|
35
|
+
parser.error(f"project root is not a directory: {root}")
|
|
36
|
+
context_path = Path(args.output).expanduser().resolve() if args.output else root / "project-context.json"
|
|
37
|
+
context = analyze(
|
|
38
|
+
str(root),
|
|
39
|
+
max_files=args.max_files,
|
|
40
|
+
max_bytes=args.max_bytes,
|
|
41
|
+
max_seconds=args.max_seconds,
|
|
42
|
+
ignore_dirs=args.ignore_dir or None,
|
|
43
|
+
ignore_globs=args.ignore_glob or None,
|
|
44
|
+
)
|
|
45
|
+
context_path.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
context_path.write_text(json.dumps(context, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
47
|
+
print(f"{context_path} written ({len(json.dumps(context, ensure_ascii=False))} bytes)")
|
|
48
|
+
memory_result = refresh_if_present(root, context_path, initialize=args.init_memory)
|
|
49
|
+
if memory_result:
|
|
50
|
+
print(json.dumps({"project_memory": memory_result}, ensure_ascii=False))
|
|
51
|
+
print("== MotionLoom analysis complete: review assumptions before generating ==")
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Cross-platform runtime telemetry capture for MotionLoom.
|
|
2
|
+
|
|
3
|
+
This is the platform-neutral replacement for capture-runtime-telemetry.sh.
|
|
4
|
+
It intentionally shells out only to the repository's npm runtime:test script,
|
|
5
|
+
using pathlib and subprocess APIs that work on Ubuntu, macOS and Windows.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
SCENE_RE = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_args() -> argparse.Namespace:
|
|
24
|
+
parser = argparse.ArgumentParser(description="Capture and verify runtime telemetry")
|
|
25
|
+
parser.add_argument("scene")
|
|
26
|
+
parser.add_argument("task_dir")
|
|
27
|
+
parser.add_argument("--max-age-days", type=int, default=1)
|
|
28
|
+
return parser.parse_args()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load_json(path: Path) -> dict:
|
|
32
|
+
try:
|
|
33
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
34
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
35
|
+
raise SystemExit(f"capture-runtime-telemetry: invalid JSON {path}: {exc}") from exc
|
|
36
|
+
if not isinstance(value, dict):
|
|
37
|
+
raise SystemExit(f"capture-runtime-telemetry: expected object in {path}")
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> int:
|
|
42
|
+
args = parse_args()
|
|
43
|
+
if not SCENE_RE.fullmatch(args.scene):
|
|
44
|
+
print(f"capture-runtime-telemetry: unsafe scene identifier: {args.scene}", file=sys.stderr)
|
|
45
|
+
return 2
|
|
46
|
+
if args.max_age_days < 0:
|
|
47
|
+
print("capture-runtime-telemetry: --max-age-days must be non-negative", file=sys.stderr)
|
|
48
|
+
return 2
|
|
49
|
+
|
|
50
|
+
root = Path(__file__).resolve().parents[1]
|
|
51
|
+
task_dir = Path(args.task_dir).expanduser()
|
|
52
|
+
task_dir = task_dir.resolve() if task_dir.is_absolute() else (root / task_dir).resolve()
|
|
53
|
+
scene_dir = (root / "src" / "output" / args.scene).resolve()
|
|
54
|
+
manifest_path = scene_dir / "manifest.json"
|
|
55
|
+
task_path = task_dir / "task.json"
|
|
56
|
+
if not manifest_path.is_file() or not task_path.is_file():
|
|
57
|
+
print("capture-runtime-telemetry: missing scene manifest or task.json", file=sys.stderr)
|
|
58
|
+
return 2
|
|
59
|
+
|
|
60
|
+
manifest = load_json(manifest_path)
|
|
61
|
+
task = load_json(task_path)
|
|
62
|
+
source = manifest.get("file")
|
|
63
|
+
task_id = task.get("task_id")
|
|
64
|
+
if not isinstance(source, str) or not source or Path(source).is_absolute() or ".." in Path(source).parts:
|
|
65
|
+
print("capture-runtime-telemetry: manifest.file must be a safe relative path", file=sys.stderr)
|
|
66
|
+
return 2
|
|
67
|
+
if not isinstance(task_id, str) or not task_id:
|
|
68
|
+
print("capture-runtime-telemetry: task.json.task_id is required", file=sys.stderr)
|
|
69
|
+
return 2
|
|
70
|
+
|
|
71
|
+
source_path = (scene_dir / source).resolve()
|
|
72
|
+
if scene_dir not in source_path.parents or not source_path.is_file():
|
|
73
|
+
print("capture-runtime-telemetry: manifest source must remain inside scene directory", file=sys.stderr)
|
|
74
|
+
return 2
|
|
75
|
+
|
|
76
|
+
output_dir = task_dir / "runtime-adapters"
|
|
77
|
+
if output_dir.exists():
|
|
78
|
+
shutil.rmtree(output_dir)
|
|
79
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
|
|
81
|
+
env = os.environ.copy()
|
|
82
|
+
env.update(
|
|
83
|
+
{
|
|
84
|
+
"RUNTIME_EVIDENCE_DIR": str(output_dir),
|
|
85
|
+
"RUNTIME_SCENE": args.scene,
|
|
86
|
+
"RUNTIME_TASK_ID": task_id,
|
|
87
|
+
"RUNTIME_SOURCE_PATH": str(source_path),
|
|
88
|
+
"RUNTIME_MANIFEST_PATH": str(manifest_path),
|
|
89
|
+
"RUNTIME_MOTION_IR_PATH": str(task_dir / "motion-ir.json"),
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
npm = "npm.cmd" if os.name == "nt" else "npm"
|
|
93
|
+
runtime = subprocess.run([npm, "run", "runtime:test"], cwd=root, env=env, check=False)
|
|
94
|
+
if runtime.returncode != 0:
|
|
95
|
+
return runtime.returncode
|
|
96
|
+
|
|
97
|
+
verifier = subprocess.run(
|
|
98
|
+
[
|
|
99
|
+
sys.executable,
|
|
100
|
+
str(root / "scripts" / "evidence-verifier.py"),
|
|
101
|
+
"--scene-dir",
|
|
102
|
+
str(scene_dir),
|
|
103
|
+
"--task-dir",
|
|
104
|
+
str(task_dir),
|
|
105
|
+
"--runtime-evidence",
|
|
106
|
+
"runtime-adapters/runtime-evidence.json",
|
|
107
|
+
"--max-age-days",
|
|
108
|
+
str(args.max_age_days),
|
|
109
|
+
"--output",
|
|
110
|
+
str(task_dir / "evidence-verifier-report.json"),
|
|
111
|
+
],
|
|
112
|
+
cwd=root,
|
|
113
|
+
check=False,
|
|
114
|
+
)
|
|
115
|
+
return verifier.returncode
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
raise SystemExit(main())
|