@warpgogol/forge 2.8.0 → 2.8.2
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.md +1 -1
- package/README.md +23 -13
- package/README.uk.md +19 -12
- package/dist/os/session/handlers/validate.d.ts.map +1 -1
- package/dist/os/session/handlers/validate.js +23 -1
- package/dist/os/session/handlers/validate.js.map +1 -1
- package/dist/os/session/types.d.ts +31 -1
- package/dist/os/session/types.d.ts.map +1 -1
- package/dist/os/session/types.js +7 -0
- package/dist/os/session/types.js.map +1 -1
- package/os/adr/adr.module.ts +158 -0
- package/os/adr/frontmatter-io.ts +80 -0
- package/os/adr/handlers/archive.ts +244 -0
- package/os/adr/handlers/implement-stamp.ts +331 -0
- package/os/adr/handlers/list-create.ts +207 -0
- package/os/adr/handlers/validate.test.ts +232 -0
- package/os/adr/handlers/validate.ts +429 -0
- package/os/adr/index.ts +33 -0
- package/os/adr/types.ts +141 -0
- package/os/audit/audit.module.ts +48 -0
- package/os/audit/frontmatter-io.ts +84 -0
- package/os/audit/handlers/archive.ts +239 -0
- package/os/audit/index.ts +23 -0
- package/os/audit/types.ts +29 -0
- package/os/compass/compass.module.ts +186 -0
- package/os/compass/handlers/compass-audit-handler.ts +382 -0
- package/os/compass/handlers/compass-change-summary-handler.ts +272 -0
- package/os/compass/handlers/compass-inventory-handler.ts +294 -0
- package/os/compass/handlers/compass-inventory.ts +521 -0
- package/os/compass/handlers/git-revision.ts +137 -0
- package/os/compass/handlers/resolve-scan-root-workpiece.test.ts +65 -0
- package/os/compass/handlers/resolve-scan-root.ts +79 -0
- package/os/compass/index.ts +33 -0
- package/os/core/core.module.ts +948 -0
- package/os/core/handlers/assets-check.ts +122 -0
- package/os/core/handlers/assets-helpers.ts +174 -0
- package/os/core/handlers/assets-list.ts +99 -0
- package/os/core/handlers/build.ts +155 -0
- package/os/core/handlers/determinism-check.ts +354 -0
- package/os/core/handlers/dev.ts +127 -0
- package/os/core/handlers/invariant-engine.test.ts +681 -0
- package/os/core/handlers/knowledge-compact.ts +248 -0
- package/os/core/handlers/lifecycle-handlers.test.ts +524 -0
- package/os/core/handlers/note-frontmatter-validate.test.ts +93 -0
- package/os/core/handlers/note-link-validate.test.ts +117 -0
- package/os/core/handlers/note-orphan-detect.test.ts +94 -0
- package/os/core/handlers/package-health.test.ts +226 -0
- package/os/core/handlers/package-health.ts +231 -0
- package/os/core/handlers/pinned-check.ts +200 -0
- package/os/core/handlers/pinned-init.ts +333 -0
- package/os/core/handlers/pinned-types.ts +50 -0
- package/os/core/handlers/pinned-validate.ts +301 -0
- package/os/core/handlers/profile-resolve.ts +92 -0
- package/os/core/handlers/release-prepare.ts +294 -0
- package/os/core/handlers/release-publish.ts +211 -0
- package/os/core/handlers/validate.ts +262 -0
- package/os/core/handlers/workspace-deps.ts +55 -0
- package/os/core/index.ts +13 -0
- package/os/exploration/exploration.module.ts +90 -0
- package/os/exploration/frontmatter-io.ts +77 -0
- package/os/exploration/handlers/archive.ts +159 -0
- package/os/exploration/handlers/list.ts +77 -0
- package/os/exploration/handlers/show.ts +107 -0
- package/os/exploration/index.ts +25 -0
- package/os/exploration/types.ts +65 -0
- package/os/mission/handlers/archive.test.ts +391 -0
- package/os/mission/handlers/archive.ts +453 -0
- package/os/mission/index.ts +1 -0
- package/os/mission/mission.module.ts +48 -0
- package/os/mission/types.ts +41 -0
- package/os/naming/index.ts +14 -0
- package/os/naming/naming-convention.test.ts +163 -0
- package/os/naming/naming-convention.ts +378 -0
- package/os/naming/naming.module.ts +36 -0
- package/os/notes/index.ts +10 -0
- package/os/notes/notes.module.ts +109 -0
- package/os/plan/frontmatter-io.ts +82 -0
- package/os/plan/handlers/archive.ts +238 -0
- package/os/plan/index.ts +23 -0
- package/os/plan/plan.module.ts +48 -0
- package/os/plan/types.ts +29 -0
- package/os/program/discovery.ts +294 -0
- package/os/program/handlers/complete.ts +419 -0
- package/os/program/handlers/lease.ts +433 -0
- package/os/program/handlers/seal.ts +332 -0
- package/os/program/handlers/validate.ts +213 -0
- package/os/program/lease.ts +156 -0
- package/os/program/program.module.ts +212 -0
- package/os/program/schemas.ts +218 -0
- package/os/program/state.ts +264 -0
- package/os/rfc/acceptance.ts +344 -0
- package/os/rfc/decision-log.ts +310 -0
- package/os/rfc/dna-trace.ts +280 -0
- package/os/rfc/frontmatter-io.test.ts +132 -0
- package/os/rfc/frontmatter-io.ts +130 -0
- package/os/rfc/handlers/archive.ts +300 -0
- package/os/rfc/handlers/check.ts +238 -0
- package/os/rfc/handlers/implement-stamp.ts +515 -0
- package/os/rfc/handlers/index-graph.ts +237 -0
- package/os/rfc/handlers/lifecycle.test.ts +127 -0
- package/os/rfc/handlers/lifecycle.ts +249 -0
- package/os/rfc/handlers/list-create.ts +296 -0
- package/os/rfc/handlers/pipeline-status.ts +173 -0
- package/os/rfc/handlers/shared.ts +121 -0
- package/os/rfc/handlers/supersede-propose.ts +241 -0
- package/os/rfc/handlers/validate-rules.test.ts +891 -0
- package/os/rfc/handlers/validate-rules.ts +984 -0
- package/os/rfc/handlers/validate.ts +166 -0
- package/os/rfc/handlers.ts +19 -0
- package/os/rfc/index.ts +97 -0
- package/os/rfc/rfc.module.ts +416 -0
- package/os/rfc/types.ts +596 -0
- package/os/rfc/verification-evidence.ts +244 -0
- package/os/session/atif-parser.ts +166 -0
- package/os/session/frontmatter-io.ts +163 -0
- package/os/session/handlers/archive.ts +288 -0
- package/os/session/handlers/list.ts +174 -0
- package/os/session/handlers/save.ts +348 -0
- package/os/session/handlers/validate.ts +250 -0
- package/os/session/index.ts +50 -0
- package/os/session/session.module.ts +126 -0
- package/os/session/types.ts +203 -0
- package/os/spec/live-spec-list-show-validate.test.ts +257 -0
- package/os/spec/live-spec-list.ts +78 -0
- package/os/spec/live-spec-merge.test.ts +261 -0
- package/os/spec/live-spec-merge.ts +413 -0
- package/os/spec/live-spec-show.ts +117 -0
- package/os/spec/live-spec-types.ts +93 -0
- package/os/spec/live-spec-validate.ts +170 -0
- package/os/spec/spec-materialize.ts +390 -0
- package/os/spec/spec-schema.ts +174 -0
- package/os/spec/spec-status.ts +284 -0
- package/os/spec/spec-validate.ts +513 -0
- package/os/spec/spec.module.ts +135 -0
- package/os/werkstatt/handlers/lock.ts +168 -0
- package/os/werkstatt/handlers/schema.ts +47 -0
- package/os/werkstatt/handlers/werkstatt-lock-recover.ts +175 -0
- package/os/werkstatt/handlers/werkstatt-lock-status.ts +73 -0
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +104 -0
- package/os/werkstatt/index.ts +39 -0
- package/os/werkstatt/werkstatt.module.ts +62 -0
- package/os/workflow/handlers.ts +304 -0
- package/os/workflow/index.ts +28 -0
- package/os/workflow/types.ts +99 -0
- package/os/workflow/workflow.module.ts +54 -0
- package/package.json +3 -3
- package/profiles/godot-csharp.yaml +4 -4
- package/profiles/knowledge-typescript-turborepo.yaml +110 -0
- package/profiles/phaser-turborepo.yaml +5 -5
- package/skills/_shared/fo-session-summary.md +132 -16
- package/skills/fo/fo-add-tests/pbt-guide.md +13 -13
- package/skills/fo/fo-doc-audit/SKILL.md +6 -6
- package/skills/fo/fo-handoff/SKILL.md +5 -0
- package/skills/fo/fo-idea/SKILL.md +1 -1
- package/skills/fo/fo-review/SKILL.md +1 -1
- package/skills/fo/fo-session-retro/SKILL.md +8 -0
- package/skills/fo/fo-session-save/SKILL.md +8 -0
- package/src/cli-output.ts +59 -0
- package/src/config/__tests__/resolve-terminology.test.ts +45 -0
- package/src/config/forge-config.ts +524 -0
- package/src/forge-module.ts +39 -0
- package/src/index.ts +172 -0
- package/src/knowledge/__tests__/promote.test.ts +219 -0
- package/src/knowledge/__tests__/serialize.test.ts +192 -0
- package/src/knowledge/budgets.ts +202 -0
- package/src/knowledge/compact.ts +405 -0
- package/src/knowledge/index.ts +53 -0
- package/src/knowledge/parse.ts +227 -0
- package/src/knowledge/promote.ts +156 -0
- package/src/knowledge/schema.ts +112 -0
- package/src/knowledge/serialize.ts +72 -0
- package/src/migration-adapters/__tests__/ignored-files.test.ts +51 -0
- package/src/migration-adapters/__tests__/registry.test.ts +131 -0
- package/src/migration-adapters/__tests__/types.test.ts +86 -0
- package/src/migration-adapters/git-utils.ts +71 -0
- package/src/migration-adapters/ignored-files.ts +221 -0
- package/src/migration-adapters/index.ts +21 -0
- package/src/migration-adapters/node-typescript-pnpm/index.ts +189 -0
- package/src/migration-adapters/phaser-pnpm/index.ts +189 -0
- package/src/migration-adapters/registry.ts +61 -0
- package/src/migration-adapters/types.ts +68 -0
- package/src/onboarding/__tests__/workspace-discovery.test.ts +134 -0
- package/src/onboarding/agents-generate.ts +443 -0
- package/src/onboarding/create.ts +427 -0
- package/src/onboarding/doctor.ts +1297 -0
- package/src/onboarding/init.ts +337 -0
- package/src/onboarding/invariant-engine.ts +448 -0
- package/src/onboarding/memory-scaffold.ts +189 -0
- package/src/onboarding/nested-agents-generate.ts +93 -0
- package/src/onboarding/nested-agents-templates.ts +233 -0
- package/src/onboarding/profile-validate.ts +139 -0
- package/src/onboarding/scaffold-project.ts +299 -0
- package/src/onboarding/scaffold.ts +143 -0
- package/src/onboarding/upgrade.ts +558 -0
- package/src/onboarding/workspace-discovery.ts +167 -0
- package/src/profiles/__tests__/profile-schema.test.ts +228 -0
- package/src/profiles/__tests__/stack-profile.test.ts +190 -0
- package/src/profiles/__tests__/terminology-utils.test.ts +71 -0
- package/src/profiles/profile-schema.ts +350 -0
- package/src/profiles/stack-profile.ts +188 -0
- package/src/profiles/terminology-utils.ts +36 -0
- package/src/registry.ts +185 -0
- package/src/skill-schema.ts +37 -0
- package/src/tests/acceptance-criteria.test.ts +99 -0
- package/src/tests/adr-implement-stamp.test.ts +368 -0
- package/src/tests/agents-generate-domain.test.ts +317 -0
- package/src/tests/agents-generate.test.ts +458 -0
- package/src/tests/bindings-schema.test.ts +314 -0
- package/src/tests/budgets.test.ts +213 -0
- package/src/tests/cli-output.test.ts +196 -0
- package/src/tests/compact.test.ts +796 -0
- package/src/tests/create.test.ts +289 -0
- package/src/tests/doctor-autonomy.test.ts +98 -0
- package/src/tests/doctor-bindings.test.ts +286 -0
- package/src/tests/doctor-domain.test.ts +113 -0
- package/src/tests/exploration-handlers.test.ts +256 -0
- package/src/tests/fixtures/agents-generate-business-before.txt +288 -0
- package/src/tests/forge-config.test.ts +420 -0
- package/src/tests/fs-atomic.test.ts +96 -0
- package/src/tests/fs.test.ts +103 -0
- package/src/tests/generated-marker.test.ts +161 -0
- package/src/tests/hash.test.ts +37 -0
- package/src/tests/implement-stamp.test.ts +505 -0
- package/src/tests/init-bindings.test.ts +183 -0
- package/src/tests/knowledge-parse.test.ts +307 -0
- package/src/tests/knowledge-pbt.test.ts +164 -0
- package/src/tests/memory-scaffold.test.ts +192 -0
- package/src/tests/migration-adapters.test.ts +480 -0
- package/src/tests/package-files.test.ts +54 -0
- package/src/tests/pinned-check.test.ts +194 -0
- package/src/tests/pinned-init.test.ts +126 -0
- package/src/tests/profile-schema.test.ts +194 -0
- package/src/tests/profile-validate.test.ts +120 -0
- package/src/tests/program-lease.test.ts +245 -0
- package/src/tests/program-paths-extended.test.ts +117 -0
- package/src/tests/program-paths.test.ts +98 -0
- package/src/tests/program-property.test.ts +139 -0
- package/src/tests/program-schemas.test.ts +445 -0
- package/src/tests/program-spec-node.test.ts +250 -0
- package/src/tests/promote.test.ts +481 -0
- package/src/tests/registry.test.ts +73 -0
- package/src/tests/scaffold-project.test.ts +150 -0
- package/src/tests/session-handlers.test.ts +503 -0
- package/src/tests/session-pbt.test.ts +190 -0
- package/src/tests/skill-schema.test.ts +146 -0
- package/src/tests/skill-validate-knowledge.test.ts +26 -0
- package/src/tests/skill-validate.test.ts +254 -0
- package/src/tests/stack-profile.test.ts +221 -0
- package/src/tests/string-utils.test.ts +42 -0
- package/src/tests/upgrade.test.ts +428 -0
- package/src/tests/werkstatt-lock.test.ts +343 -0
- package/src/tests/workspace-discovery-domain.test.ts +112 -0
- package/src/tests/workspace-discovery.test.ts +135 -0
- package/src/types.ts +221 -0
- package/src/utils/__tests__/fs-idempotent.test.ts +73 -0
- package/src/utils/__tests__/fs-trash.test.ts +45 -0
- package/src/utils/fs-atomic.ts +93 -0
- package/src/utils/fs-idempotent.ts +41 -0
- package/src/utils/fs-trash-sync.ts +37 -0
- package/src/utils/fs-trash.ts +24 -0
- package/src/utils/fs.ts +74 -0
- package/src/utils/generated-marker.ts +166 -0
- package/src/utils/hash.ts +19 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/string-utils.ts +19 -0
- package/src/validators/__tests__/note-orphan-detect.test.ts +94 -0
- package/src/validators/note-frontmatter-validate.ts +142 -0
- package/src/validators/note-link-validate.ts +145 -0
- package/src/validators/note-orphan-detect.ts +146 -0
- package/src/validators/port-validate.ts +97 -0
- package/src/validators/skill-validate.ts +831 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>forge.assets.check — check for missing, orphaned, and unreferenced assets. Supports --dry-run, --strict, --json, --profile.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement asset listing logic — that lives in assets-list.ts.</item>
|
|
6
|
+
<item>Do not import from @warpgogol/* in autonomous modules — os/core/ may import from @warpgogol/*.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0679: initial forge.assets.check handler with missing/orphaned detection, --strict, --dry-run.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
ForgeCommandInput,
|
|
16
|
+
ForgeCommandResult,
|
|
17
|
+
ForgeRuntimeContext,
|
|
18
|
+
} from "../../../src/types.ts";
|
|
19
|
+
import { resolveActiveProfile, resolveLifecycleFlags } from "./profile-resolve.ts";
|
|
20
|
+
import {
|
|
21
|
+
scanAssets,
|
|
22
|
+
extractReferences,
|
|
23
|
+
findMissingAssets,
|
|
24
|
+
findOrphanedAssets,
|
|
25
|
+
} from "./assets-helpers.ts";
|
|
26
|
+
|
|
27
|
+
export interface AssetCheckResult {
|
|
28
|
+
missing: Array<{ path: string; referencedBy: string[] }>;
|
|
29
|
+
orphaned: Array<{ path: string; type: string }>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ForgeAssetsCheckResult {
|
|
33
|
+
command: "forge.assets.check";
|
|
34
|
+
profileId: string;
|
|
35
|
+
check: AssetCheckResult;
|
|
36
|
+
allOk: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runAssetsCheck(
|
|
40
|
+
input: ForgeCommandInput,
|
|
41
|
+
context: ForgeRuntimeContext,
|
|
42
|
+
): Promise<ForgeCommandResult<ForgeAssetsCheckResult>> {
|
|
43
|
+
const { workspaceRoot, logger } = context;
|
|
44
|
+
const { dryRun, profileIdOverride } = resolveLifecycleFlags(input, context);
|
|
45
|
+
const strict = input.flags["strict"] === true;
|
|
46
|
+
|
|
47
|
+
const resolved = resolveActiveProfile(workspaceRoot, context.forgeRoot, profileIdOverride);
|
|
48
|
+
if (!resolved) {
|
|
49
|
+
return {
|
|
50
|
+
data: {
|
|
51
|
+
command: "forge.assets.check",
|
|
52
|
+
profileId: "",
|
|
53
|
+
check: { missing: [], orphaned: [] },
|
|
54
|
+
allOk: false,
|
|
55
|
+
},
|
|
56
|
+
exitCode: 1,
|
|
57
|
+
summary: "No active profile found. Set `profile` in forge.yaml or use --profile <id>.",
|
|
58
|
+
nextSteps: [{ action: "Set profile in forge.yaml or use --profile <id>", kind: "required" }],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { profile } = resolved;
|
|
63
|
+
|
|
64
|
+
if (!profile.assets) {
|
|
65
|
+
return {
|
|
66
|
+
data: {
|
|
67
|
+
command: "forge.assets.check",
|
|
68
|
+
profileId: profile.id,
|
|
69
|
+
check: { missing: [], orphaned: [] },
|
|
70
|
+
allOk: true,
|
|
71
|
+
},
|
|
72
|
+
exitCode: 0,
|
|
73
|
+
summary: `Profile ${profile.id} does not declare assets — nothing to check.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const assets = await scanAssets(workspaceRoot, profile.assets, { dryRun });
|
|
78
|
+
const compositionExtensions = profile.artifacts?.flatMap((a) => a.extensions) ?? [];
|
|
79
|
+
const refMap = await extractReferences(workspaceRoot, profile.assets, compositionExtensions);
|
|
80
|
+
|
|
81
|
+
const missing = findMissingAssets(refMap, assets);
|
|
82
|
+
const orphaned = findOrphanedAssets(assets, refMap);
|
|
83
|
+
|
|
84
|
+
const hasMissing = missing.length > 0;
|
|
85
|
+
const hasOrphaned = orphaned.length > 0;
|
|
86
|
+
const allOk = !hasMissing && (!hasOrphaned || !strict);
|
|
87
|
+
|
|
88
|
+
if (hasMissing) {
|
|
89
|
+
for (const m of missing) {
|
|
90
|
+
logger.error(` missing: ${m.path} (referenced by ${m.referencedBy.join(", ")})`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (hasOrphaned) {
|
|
94
|
+
for (const o of orphaned) {
|
|
95
|
+
if (strict) {
|
|
96
|
+
logger.error(` orphaned: ${o.path} (${o.type})`);
|
|
97
|
+
} else {
|
|
98
|
+
logger.warn(` orphaned: ${o.path} (${o.type})`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (allOk) {
|
|
104
|
+
logger.success(` all assets OK`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parts: string[] = [];
|
|
108
|
+
if (hasMissing) parts.push(`${missing.length} missing`);
|
|
109
|
+
if (hasOrphaned) parts.push(`${orphaned.length} orphaned`);
|
|
110
|
+
const summary = parts.length === 0 ? "all assets OK" : parts.join(", ");
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
data: {
|
|
114
|
+
command: "forge.assets.check",
|
|
115
|
+
profileId: profile.id,
|
|
116
|
+
check: { missing, orphaned },
|
|
117
|
+
allOk,
|
|
118
|
+
},
|
|
119
|
+
exitCode: allOk ? 0 : 1,
|
|
120
|
+
summary: dryRun ? `[dry-run] ${summary}` : summary,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Shared asset scanning and reference extraction logic for forge.assets.list and forge.assets.check (RFC-0679).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement command registration — that lives in core.module.ts.</item>
|
|
6
|
+
<item>Do not import from @warpgogol/* in autonomous modules — os/core/ may import from @warpgogol/*.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0679: initial shared asset helpers — scanAssets, extractReferences, classifyAssetType.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFile, stat } from "node:fs/promises";
|
|
15
|
+
import { join, relative, extname } from "node:path";
|
|
16
|
+
import { loadWorkspaceDeps } from "./workspace-deps.ts";
|
|
17
|
+
import type { ProfileAsset, ProfileAssetType } from "../../../src/profiles/profile-schema.ts";
|
|
18
|
+
|
|
19
|
+
export interface AssetEntry {
|
|
20
|
+
path: string;
|
|
21
|
+
type: string;
|
|
22
|
+
size: number;
|
|
23
|
+
hash: string;
|
|
24
|
+
referencedBy: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AssetReference {
|
|
28
|
+
path: string;
|
|
29
|
+
referencedBy: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function classifyAssetType(filePath: string, types: ProfileAssetType[]): string | undefined {
|
|
33
|
+
const ext = extname(filePath).toLowerCase();
|
|
34
|
+
return types.find((t) => t.extensions.some((e) => e.toLowerCase() === ext))?.id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function scanAssets(
|
|
38
|
+
workspaceRoot: string,
|
|
39
|
+
assetsConfig: ProfileAsset,
|
|
40
|
+
options: { dryRun?: boolean; typeFilter?: string } = {},
|
|
41
|
+
): Promise<AssetEntry[]> {
|
|
42
|
+
const { collectFiles, byteHashFile } = await loadWorkspaceDeps();
|
|
43
|
+
const assetsDir = join(workspaceRoot, assetsConfig.dir);
|
|
44
|
+
const allFiles = await collectFiles(assetsDir, {
|
|
45
|
+
ignore: (name) => name.startsWith("-") || name.startsWith("old-") || name === ".DS_Store",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const filtered = allFiles
|
|
49
|
+
.map((abs) => relative(workspaceRoot, abs))
|
|
50
|
+
.filter((rel) => {
|
|
51
|
+
if (options.typeFilter) {
|
|
52
|
+
const type = classifyAssetType(rel, assetsConfig.types);
|
|
53
|
+
return type === options.typeFilter;
|
|
54
|
+
}
|
|
55
|
+
return classifyAssetType(rel, assetsConfig.types) !== undefined;
|
|
56
|
+
})
|
|
57
|
+
.sort();
|
|
58
|
+
|
|
59
|
+
const entries: AssetEntry[] = [];
|
|
60
|
+
for (const relPath of filtered) {
|
|
61
|
+
const absPath = join(workspaceRoot, relPath);
|
|
62
|
+
let size = 0;
|
|
63
|
+
let hash = "";
|
|
64
|
+
|
|
65
|
+
if (!options.dryRun) {
|
|
66
|
+
const stats = await stat(absPath);
|
|
67
|
+
size = stats.size;
|
|
68
|
+
hash = await byteHashFile(absPath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
entries.push({
|
|
72
|
+
path: relPath,
|
|
73
|
+
type: classifyAssetType(relPath, assetsConfig.types)!,
|
|
74
|
+
size,
|
|
75
|
+
hash,
|
|
76
|
+
referencedBy: [],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return entries;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function extractReferences(
|
|
84
|
+
workspaceRoot: string,
|
|
85
|
+
assetsConfig: ProfileAsset,
|
|
86
|
+
compositionExtensions: string[],
|
|
87
|
+
): Promise<Map<string, string[]>> {
|
|
88
|
+
const refMap = new Map<string, string[]>();
|
|
89
|
+
|
|
90
|
+
const { collectFiles } = await loadWorkspaceDeps();
|
|
91
|
+
const allFiles = await collectFiles(workspaceRoot, {
|
|
92
|
+
extensions: compositionExtensions,
|
|
93
|
+
ignore: (name) =>
|
|
94
|
+
name.startsWith("-") ||
|
|
95
|
+
name.startsWith("old-") ||
|
|
96
|
+
name === "node_modules" ||
|
|
97
|
+
name === "dist" ||
|
|
98
|
+
name === ".turbo" ||
|
|
99
|
+
name === ".cache" ||
|
|
100
|
+
name === ".git",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
for (const absPath of allFiles) {
|
|
104
|
+
const relPath = relative(workspaceRoot, absPath);
|
|
105
|
+
try {
|
|
106
|
+
const content = await readFile(absPath, "utf8");
|
|
107
|
+
|
|
108
|
+
for (const assetType of assetsConfig.types) {
|
|
109
|
+
if (!assetType.referencePattern) continue;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const regex = new RegExp(assetType.referencePattern, "g");
|
|
113
|
+
let match: RegExpExecArray | null;
|
|
114
|
+
const MAX_MATCHES = 1000;
|
|
115
|
+
let matchCount = 0;
|
|
116
|
+
|
|
117
|
+
while ((match = regex.exec(content)) !== null && matchCount < MAX_MATCHES) {
|
|
118
|
+
matchCount++;
|
|
119
|
+
const refPath = match[1] ?? match[0];
|
|
120
|
+
const normalized = refPath.startsWith("/") ? refPath.slice(1) : refPath;
|
|
121
|
+
|
|
122
|
+
const existing = refMap.get(normalized) ?? [];
|
|
123
|
+
if (!existing.includes(relPath)) {
|
|
124
|
+
existing.push(relPath);
|
|
125
|
+
}
|
|
126
|
+
refMap.set(normalized, existing);
|
|
127
|
+
|
|
128
|
+
if (regex.lastIndex === match.index) regex.lastIndex++;
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// Invalid regex — skip this type
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// Cannot read file — skip
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return refMap;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function mergeReferences(assets: AssetEntry[], refMap: Map<string, string[]>): AssetEntry[] {
|
|
143
|
+
return assets.map((asset) => {
|
|
144
|
+
const refs = refMap.get(asset.path) ?? [];
|
|
145
|
+
return { ...asset, referencedBy: refs };
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function findMissingAssets(
|
|
150
|
+
refMap: Map<string, string[]>,
|
|
151
|
+
assets: AssetEntry[],
|
|
152
|
+
): Array<{ path: string; referencedBy: string[] }> {
|
|
153
|
+
const assetPaths = new Set(assets.map((a) => a.path));
|
|
154
|
+
const missing: Array<{ path: string; referencedBy: string[] }> = [];
|
|
155
|
+
|
|
156
|
+
for (const [refPath, referencedBy] of refMap) {
|
|
157
|
+
if (!assetPaths.has(refPath)) {
|
|
158
|
+
missing.push({ path: refPath, referencedBy });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return missing.sort((a, b) => a.path.localeCompare(b.path));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function findOrphanedAssets(
|
|
166
|
+
assets: AssetEntry[],
|
|
167
|
+
refMap: Map<string, string[]>,
|
|
168
|
+
): Array<{ path: string; type: string }> {
|
|
169
|
+
const referencedAssetPaths = new Set(refMap.keys());
|
|
170
|
+
return assets
|
|
171
|
+
.filter((a) => !referencedAssetPaths.has(a.path))
|
|
172
|
+
.map((a) => ({ path: a.path, type: a.type }))
|
|
173
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
174
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>forge.assets.list — list all assets declared in the active stack profile, grouped by type. Supports --dry-run, --type, --json, --profile.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement asset checking logic — that lives in assets-check.ts.</item>
|
|
6
|
+
<item>Do not import from @warpgogol/* in autonomous modules — os/core/ may import from @warpgogol/*.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0679: initial forge.assets.list handler with profile resolution, --dry-run, --type filtering.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
ForgeCommandInput,
|
|
16
|
+
ForgeCommandResult,
|
|
17
|
+
ForgeRuntimeContext,
|
|
18
|
+
} from "../../../src/types.ts";
|
|
19
|
+
import { resolveActiveProfile, resolveLifecycleFlags } from "./profile-resolve.ts";
|
|
20
|
+
import {
|
|
21
|
+
scanAssets,
|
|
22
|
+
extractReferences,
|
|
23
|
+
mergeReferences,
|
|
24
|
+
type AssetEntry,
|
|
25
|
+
} from "./assets-helpers.ts";
|
|
26
|
+
|
|
27
|
+
export interface ForgeAssetsListResult {
|
|
28
|
+
command: "forge.assets.list";
|
|
29
|
+
profileId: string;
|
|
30
|
+
assets: AssetEntry[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function runAssetsList(
|
|
34
|
+
input: ForgeCommandInput,
|
|
35
|
+
context: ForgeRuntimeContext,
|
|
36
|
+
): Promise<ForgeCommandResult<ForgeAssetsListResult>> {
|
|
37
|
+
const { workspaceRoot, logger } = context;
|
|
38
|
+
const { dryRun, profileIdOverride } = resolveLifecycleFlags(input, context);
|
|
39
|
+
const typeFilter =
|
|
40
|
+
typeof input.flags["type"] === "string" ? (input.flags["type"] as string) : undefined;
|
|
41
|
+
|
|
42
|
+
const resolved = resolveActiveProfile(workspaceRoot, context.forgeRoot, profileIdOverride);
|
|
43
|
+
if (!resolved) {
|
|
44
|
+
return {
|
|
45
|
+
data: {
|
|
46
|
+
command: "forge.assets.list",
|
|
47
|
+
profileId: "",
|
|
48
|
+
assets: [],
|
|
49
|
+
},
|
|
50
|
+
exitCode: 1,
|
|
51
|
+
summary: "No active profile found. Set `profile` in forge.yaml or use --profile <id>.",
|
|
52
|
+
nextSteps: [{ action: "Set profile in forge.yaml or use --profile <id>", kind: "required" }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { profile } = resolved;
|
|
57
|
+
|
|
58
|
+
if (!profile.assets) {
|
|
59
|
+
return {
|
|
60
|
+
data: {
|
|
61
|
+
command: "forge.assets.list",
|
|
62
|
+
profileId: profile.id,
|
|
63
|
+
assets: [],
|
|
64
|
+
},
|
|
65
|
+
exitCode: 0,
|
|
66
|
+
summary: `Profile ${profile.id} does not declare assets — nothing to list.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const assets = await scanAssets(workspaceRoot, profile.assets, {
|
|
71
|
+
dryRun,
|
|
72
|
+
typeFilter,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const compositionExtensions = profile.artifacts?.flatMap((a) => a.extensions) ?? [];
|
|
76
|
+
const refMap = await extractReferences(workspaceRoot, profile.assets, compositionExtensions);
|
|
77
|
+
const assetsWithRefs = mergeReferences(assets, refMap);
|
|
78
|
+
|
|
79
|
+
if (dryRun) {
|
|
80
|
+
logger.info(`[dry-run] forge.assets.list — profile: ${profile.id}`);
|
|
81
|
+
for (const asset of assetsWithRefs) {
|
|
82
|
+
logger.info(` ${asset.type}: ${asset.path}`);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
logger.info(`forge.assets.list — profile: ${profile.id}`);
|
|
86
|
+
for (const asset of assetsWithRefs) {
|
|
87
|
+
logger.info(` ${asset.type}: ${asset.path} (${asset.size} bytes, ${asset.hash})`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
data: {
|
|
93
|
+
command: "forge.assets.list",
|
|
94
|
+
profileId: profile.id,
|
|
95
|
+
assets: assetsWithRefs,
|
|
96
|
+
},
|
|
97
|
+
summary: `${assetsWithRefs.length} asset(s) found${typeFilter ? ` (type: ${typeFilter})` : ""}${dryRun ? " [dry-run]" : ""}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>forge.build — execute produce commands for all artifacts declared in the active stack profile. Supports --dry-run and --json.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement dev or validate logic — those are separate handlers.</item>
|
|
6
|
+
<item>Do not import from @warpgogol/* — this module is portable.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0674: initial forge.build handler with profile resolution, --dry-run, and per-artifact execution.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { exec } from "node:child_process";
|
|
15
|
+
import { promisify } from "node:util";
|
|
16
|
+
import type {
|
|
17
|
+
ForgeCommandInput,
|
|
18
|
+
ForgeCommandResult,
|
|
19
|
+
ForgeRuntimeContext,
|
|
20
|
+
} from "../../../src/types.ts";
|
|
21
|
+
import { resolveActiveProfile, resolveLifecycleFlags } from "./profile-resolve.ts";
|
|
22
|
+
|
|
23
|
+
const execAsync = promisify(exec);
|
|
24
|
+
|
|
25
|
+
export interface ForgeBuildArtifactResult {
|
|
26
|
+
id: string;
|
|
27
|
+
command: string;
|
|
28
|
+
output?: string;
|
|
29
|
+
exitCode: number;
|
|
30
|
+
stdout: string;
|
|
31
|
+
stderr: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ForgeBuildResult {
|
|
35
|
+
command: "forge.build";
|
|
36
|
+
profileId: string;
|
|
37
|
+
artifacts: ForgeBuildArtifactResult[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runBuild(
|
|
41
|
+
input: ForgeCommandInput,
|
|
42
|
+
context: ForgeRuntimeContext,
|
|
43
|
+
): Promise<ForgeCommandResult<ForgeBuildResult>> {
|
|
44
|
+
const { workspaceRoot, logger } = context;
|
|
45
|
+
const { dryRun, profileIdOverride } = resolveLifecycleFlags(input, context);
|
|
46
|
+
|
|
47
|
+
const resolved = resolveActiveProfile(workspaceRoot, context.forgeRoot, profileIdOverride);
|
|
48
|
+
if (!resolved) {
|
|
49
|
+
return {
|
|
50
|
+
data: {
|
|
51
|
+
command: "forge.build",
|
|
52
|
+
profileId: "",
|
|
53
|
+
artifacts: [],
|
|
54
|
+
},
|
|
55
|
+
exitCode: 1,
|
|
56
|
+
summary: "No active profile found. Set `profile` in forge.yaml or use --profile <id>.",
|
|
57
|
+
nextSteps: [{ action: "Set profile in forge.yaml or use --profile <id>", kind: "required" }],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { profile } = resolved;
|
|
62
|
+
|
|
63
|
+
if (!profile.artifacts || profile.artifacts.length === 0) {
|
|
64
|
+
return {
|
|
65
|
+
data: {
|
|
66
|
+
command: "forge.build",
|
|
67
|
+
profileId: profile.id,
|
|
68
|
+
artifacts: [],
|
|
69
|
+
},
|
|
70
|
+
exitCode: 1,
|
|
71
|
+
summary: `Profile ${profile.id} does not declare any artifacts.`,
|
|
72
|
+
nextSteps: [{ action: `Add artifacts section to profile ${profile.id}`, kind: "required" }],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
const commands = profile.artifacts
|
|
78
|
+
.filter((a) => a.produce)
|
|
79
|
+
.map((a) => ({ artifactId: a.id, command: a.produce!.command, output: a.produce!.output }));
|
|
80
|
+
logger.info(`[dry-run] forge.build — profile: ${profile.id}`);
|
|
81
|
+
for (const cmd of commands) {
|
|
82
|
+
logger.info(` ${cmd.artifactId}: ${cmd.command}`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
data: {
|
|
86
|
+
command: "forge.build",
|
|
87
|
+
profileId: profile.id,
|
|
88
|
+
artifacts: commands.map((c) => ({
|
|
89
|
+
id: c.artifactId,
|
|
90
|
+
command: c.command,
|
|
91
|
+
output: c.output,
|
|
92
|
+
exitCode: 0,
|
|
93
|
+
stdout: "",
|
|
94
|
+
stderr: "",
|
|
95
|
+
})),
|
|
96
|
+
},
|
|
97
|
+
summary: `[dry-run] ${commands.length} produce command(s) resolved`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const results: ForgeBuildArtifactResult[] = [];
|
|
102
|
+
let hasFailure = false;
|
|
103
|
+
|
|
104
|
+
for (const artifact of profile.artifacts) {
|
|
105
|
+
if (!artifact.produce) {
|
|
106
|
+
logger.warn(`Skipping artifact ${artifact.id}: no produce command declared`);
|
|
107
|
+
results.push({
|
|
108
|
+
id: artifact.id,
|
|
109
|
+
command: "",
|
|
110
|
+
exitCode: 0,
|
|
111
|
+
stdout: "",
|
|
112
|
+
stderr: "skipped: no produce command",
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const cmd = artifact.produce.command;
|
|
118
|
+
try {
|
|
119
|
+
const { stdout, stderr } = await execAsync(cmd, { cwd: workspaceRoot });
|
|
120
|
+
results.push({
|
|
121
|
+
id: artifact.id,
|
|
122
|
+
command: cmd,
|
|
123
|
+
output: artifact.produce.output,
|
|
124
|
+
exitCode: 0,
|
|
125
|
+
stdout,
|
|
126
|
+
stderr,
|
|
127
|
+
});
|
|
128
|
+
logger.success(` ${artifact.id}: OK`);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const e = err as { code?: number; stdout?: string; stderr?: string; message: string };
|
|
131
|
+
results.push({
|
|
132
|
+
id: artifact.id,
|
|
133
|
+
command: cmd,
|
|
134
|
+
output: artifact.produce.output,
|
|
135
|
+
exitCode: e.code ?? 1,
|
|
136
|
+
stdout: e.stdout ?? "",
|
|
137
|
+
stderr: e.stderr ?? e.message,
|
|
138
|
+
});
|
|
139
|
+
hasFailure = true;
|
|
140
|
+
logger.error(` ${artifact.id}: FAILED (exit ${e.code ?? 1})`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
data: {
|
|
146
|
+
command: "forge.build",
|
|
147
|
+
profileId: profile.id,
|
|
148
|
+
artifacts: results,
|
|
149
|
+
},
|
|
150
|
+
exitCode: hasFailure ? 1 : 0,
|
|
151
|
+
summary: hasFailure
|
|
152
|
+
? `forge.build: ${results.filter((r) => r.exitCode !== 0).length} artifact(s) failed`
|
|
153
|
+
: `forge.build: all ${results.length} artifact(s) succeeded`,
|
|
154
|
+
};
|
|
155
|
+
}
|