@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,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
Shared exploration note frontmatter I/O — list exploration files and parse
|
|
5
|
+
their YAML frontmatter without validating the shape.
|
|
6
|
+
</purpose>
|
|
7
|
+
<non-goals>
|
|
8
|
+
<item>Do not validate frontmatter shape — that is the skill's responsibility.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0710: initial exploration frontmatter I/O.</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs/promises";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import YAML from "yaml";
|
|
19
|
+
import { EXPLORATION_DIR } from "./types.ts";
|
|
20
|
+
|
|
21
|
+
export interface ParsedExplorationNote {
|
|
22
|
+
frontmatter: Record<string, unknown>;
|
|
23
|
+
body: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseExplorationFile(source: string): ParsedExplorationNote {
|
|
27
|
+
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
28
|
+
if (!match) {
|
|
29
|
+
return { frontmatter: {}, body: source };
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
frontmatter: (YAML.parse(match[1]!) ?? {}) as Record<string, unknown>,
|
|
33
|
+
body: match[2] ?? "",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function listExplorationFiles(explorationsDirPath: string): Promise<string[]> {
|
|
38
|
+
const results: string[] = [];
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const entries = await fs.readdir(explorationsDirPath, { withFileTypes: true });
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
|
|
44
|
+
results.push(entry.name);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// Directory doesn't exist or is unreadable — return empty
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return results.sort();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function readAndParseExplorationNote(
|
|
55
|
+
explorationsDirPath: string,
|
|
56
|
+
fileName: string,
|
|
57
|
+
): Promise<{ fileName: string; parsed: ParsedExplorationNote } | undefined> {
|
|
58
|
+
try {
|
|
59
|
+
const filePath = path.join(explorationsDirPath, fileName);
|
|
60
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
61
|
+
return { fileName, parsed: parseExplorationFile(content) };
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function serializeExplorationNote(
|
|
68
|
+
frontmatter: Record<string, unknown>,
|
|
69
|
+
body: string,
|
|
70
|
+
): string {
|
|
71
|
+
const fmYaml = YAML.stringify(frontmatter).trimEnd();
|
|
72
|
+
return `---\n${fmYaml}\n---\n${body}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getExplorationsDir(workspaceRoot: string): string {
|
|
76
|
+
return path.join(workspaceRoot, EXPLORATION_DIR);
|
|
77
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>exploration.archive handler — transition an exploration note to archived status.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not delete exploration notes — archive is a status transition only.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0710: initial exploration.archive handler.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import type {
|
|
16
|
+
ForgeCommandInput,
|
|
17
|
+
ForgeCommandResult,
|
|
18
|
+
ForgeRuntimeContext,
|
|
19
|
+
} from "../../../src/types.ts";
|
|
20
|
+
import {
|
|
21
|
+
readAndParseExplorationNote,
|
|
22
|
+
serializeExplorationNote,
|
|
23
|
+
getExplorationsDir,
|
|
24
|
+
} from "../frontmatter-io.ts";
|
|
25
|
+
import { EXPLORATION_SLUG_PATTERN, type ExplorationArchiveResult } from "../types.ts";
|
|
26
|
+
|
|
27
|
+
function parseRelatedArray(value: unknown): string[] {
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return value.filter((v): v is string => typeof v === "string");
|
|
30
|
+
}
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function runExplorationArchive(
|
|
35
|
+
input: ForgeCommandInput,
|
|
36
|
+
context: ForgeRuntimeContext,
|
|
37
|
+
): Promise<ForgeCommandResult<ExplorationArchiveResult>> {
|
|
38
|
+
const { workspaceRoot, logger, outputFormat } = context;
|
|
39
|
+
const explorationsDirPath = getExplorationsDir(workspaceRoot);
|
|
40
|
+
|
|
41
|
+
const slug = input.flags["id"] as string | undefined;
|
|
42
|
+
if (!slug) {
|
|
43
|
+
return {
|
|
44
|
+
data: {
|
|
45
|
+
command: "exploration.archive",
|
|
46
|
+
status: "error",
|
|
47
|
+
id: "",
|
|
48
|
+
previousStatus: "",
|
|
49
|
+
newStatus: "archived",
|
|
50
|
+
related: [],
|
|
51
|
+
},
|
|
52
|
+
exitCode: 1,
|
|
53
|
+
summary: "exploration.archive requires --id <slug>",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!EXPLORATION_SLUG_PATTERN.test(slug)) {
|
|
58
|
+
if (outputFormat === "pretty") {
|
|
59
|
+
logger.info(`Invalid slug: ${slug}. Must be kebab-case, lowercase, latin-only.`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
data: {
|
|
63
|
+
command: "exploration.archive",
|
|
64
|
+
status: "error",
|
|
65
|
+
id: slug,
|
|
66
|
+
previousStatus: "",
|
|
67
|
+
newStatus: "archived",
|
|
68
|
+
related: [],
|
|
69
|
+
},
|
|
70
|
+
exitCode: 1,
|
|
71
|
+
summary: `Invalid slug: ${slug}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const fileName = `${slug}.md`;
|
|
76
|
+
const filePath = path.join(explorationsDirPath, fileName);
|
|
77
|
+
const result = await readAndParseExplorationNote(explorationsDirPath, fileName);
|
|
78
|
+
if (!result) {
|
|
79
|
+
if (outputFormat === "pretty") {
|
|
80
|
+
logger.info(`Exploration note not found: ${slug}`);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
data: {
|
|
84
|
+
command: "exploration.archive",
|
|
85
|
+
status: "error",
|
|
86
|
+
id: slug,
|
|
87
|
+
previousStatus: "",
|
|
88
|
+
newStatus: "archived",
|
|
89
|
+
related: [],
|
|
90
|
+
},
|
|
91
|
+
exitCode: 1,
|
|
92
|
+
summary: `Exploration note not found: ${slug}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const fm = result.parsed.frontmatter;
|
|
97
|
+
const previousStatus = String(fm["status"] ?? "open");
|
|
98
|
+
const related = parseRelatedArray(fm["related"]);
|
|
99
|
+
|
|
100
|
+
const rfcId = input.flags["rfc"] as string | undefined;
|
|
101
|
+
if (rfcId && !related.includes(rfcId)) {
|
|
102
|
+
related.push(rfcId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (previousStatus === "archived") {
|
|
106
|
+
if (rfcId) {
|
|
107
|
+
const updatedFrontmatter: Record<string, unknown> = {
|
|
108
|
+
...fm,
|
|
109
|
+
status: "archived",
|
|
110
|
+
related,
|
|
111
|
+
};
|
|
112
|
+
const updatedContent = serializeExplorationNote(updatedFrontmatter, result.parsed.body);
|
|
113
|
+
await fs.writeFile(filePath, updatedContent, "utf-8");
|
|
114
|
+
}
|
|
115
|
+
if (outputFormat === "pretty") {
|
|
116
|
+
logger.info(`Exploration note ${slug} is already archived (no-op).`);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
data: {
|
|
120
|
+
command: "exploration.archive",
|
|
121
|
+
status: "ok",
|
|
122
|
+
id: slug,
|
|
123
|
+
previousStatus: "archived",
|
|
124
|
+
newStatus: "archived",
|
|
125
|
+
related,
|
|
126
|
+
},
|
|
127
|
+
exitCode: 0,
|
|
128
|
+
summary: `Exploration note ${slug} is already archived (no-op)`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const updatedFrontmatter: Record<string, unknown> = {
|
|
133
|
+
...fm,
|
|
134
|
+
status: "archived",
|
|
135
|
+
related,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const updatedContent = serializeExplorationNote(updatedFrontmatter, result.parsed.body);
|
|
139
|
+
await fs.writeFile(filePath, updatedContent, "utf-8");
|
|
140
|
+
|
|
141
|
+
if (outputFormat === "pretty") {
|
|
142
|
+
logger.success(`Archived exploration note: ${slug} (${previousStatus} → archived)`);
|
|
143
|
+
if (rfcId) {
|
|
144
|
+
logger.info(`Related RFC: ${rfcId}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
data: {
|
|
150
|
+
command: "exploration.archive",
|
|
151
|
+
status: "ok",
|
|
152
|
+
id: slug,
|
|
153
|
+
previousStatus,
|
|
154
|
+
newStatus: "archived",
|
|
155
|
+
related,
|
|
156
|
+
},
|
|
157
|
+
summary: `Archived exploration note: ${slug} (${previousStatus} → archived)`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>exploration.list handler — list all exploration notes with metadata.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not validate frontmatter shape — listing is metadata-only.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0710: initial exploration.list handler.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import type {
|
|
15
|
+
ForgeCommandInput,
|
|
16
|
+
ForgeCommandResult,
|
|
17
|
+
ForgeRuntimeContext,
|
|
18
|
+
} from "../../../src/types.ts";
|
|
19
|
+
import {
|
|
20
|
+
listExplorationFiles,
|
|
21
|
+
readAndParseExplorationNote,
|
|
22
|
+
getExplorationsDir,
|
|
23
|
+
} from "../frontmatter-io.ts";
|
|
24
|
+
import type { ExplorationListEntry, ExplorationListResult, ExplorationStatus } from "../types.ts";
|
|
25
|
+
|
|
26
|
+
export async function runExplorationList(
|
|
27
|
+
input: ForgeCommandInput,
|
|
28
|
+
context: ForgeRuntimeContext,
|
|
29
|
+
): Promise<ForgeCommandResult<ExplorationListResult>> {
|
|
30
|
+
const { workspaceRoot, logger, outputFormat } = context;
|
|
31
|
+
const explorationsDirPath = getExplorationsDir(workspaceRoot);
|
|
32
|
+
|
|
33
|
+
const filterStatus = input.flags["status"] as string | undefined;
|
|
34
|
+
|
|
35
|
+
const files = await listExplorationFiles(explorationsDirPath);
|
|
36
|
+
const entries: ExplorationListEntry[] = [];
|
|
37
|
+
|
|
38
|
+
for (const fileName of files) {
|
|
39
|
+
const result = await readAndParseExplorationNote(explorationsDirPath, fileName);
|
|
40
|
+
if (!result) continue;
|
|
41
|
+
|
|
42
|
+
const fm = result.parsed.frontmatter;
|
|
43
|
+
const status = String(fm["status"] ?? "") as ExplorationStatus;
|
|
44
|
+
|
|
45
|
+
if (filterStatus && status !== filterStatus) continue;
|
|
46
|
+
|
|
47
|
+
entries.push({
|
|
48
|
+
id: String(fm["id"] ?? path.basename(fileName, ".md")),
|
|
49
|
+
title: String(fm["title"] ?? ""),
|
|
50
|
+
status,
|
|
51
|
+
createdAt: String(fm["createdAt"] ?? ""),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (outputFormat === "pretty") {
|
|
56
|
+
if (entries.length === 0) {
|
|
57
|
+
logger.info("No exploration notes found.");
|
|
58
|
+
} else {
|
|
59
|
+
logger.section(`Exploration notes (${entries.length})`);
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
logger.info(
|
|
62
|
+
`${entry.id} ${entry.status.padEnd(10)} ${entry.createdAt.padEnd(12)} ${entry.title}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
data: {
|
|
70
|
+
command: "exploration.list",
|
|
71
|
+
status: "ok",
|
|
72
|
+
count: entries.length,
|
|
73
|
+
explorations: entries,
|
|
74
|
+
},
|
|
75
|
+
summary: `${entries.length} exploration note(s) found`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>exploration.show handler — return the full content of a single exploration note.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not validate frontmatter shape — show returns raw parsed content.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0710: initial exploration.show handler.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
ForgeCommandInput,
|
|
15
|
+
ForgeCommandResult,
|
|
16
|
+
ForgeRuntimeContext,
|
|
17
|
+
} from "../../../src/types.ts";
|
|
18
|
+
import { readAndParseExplorationNote, getExplorationsDir } from "../frontmatter-io.ts";
|
|
19
|
+
import type { ExplorationNote, ExplorationShowResult, ExplorationStatus } from "../types.ts";
|
|
20
|
+
|
|
21
|
+
function parseRelatedArray(value: unknown): string[] {
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return value.filter((v): v is string => typeof v === "string");
|
|
24
|
+
}
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function runExplorationShow(
|
|
29
|
+
input: ForgeCommandInput,
|
|
30
|
+
context: ForgeRuntimeContext,
|
|
31
|
+
): Promise<ForgeCommandResult<ExplorationShowResult>> {
|
|
32
|
+
const { workspaceRoot, logger, outputFormat } = context;
|
|
33
|
+
const explorationsDirPath = getExplorationsDir(workspaceRoot);
|
|
34
|
+
|
|
35
|
+
const slug = input.flags["id"] as string | undefined;
|
|
36
|
+
if (!slug) {
|
|
37
|
+
return {
|
|
38
|
+
data: {
|
|
39
|
+
command: "exploration.show",
|
|
40
|
+
status: "error",
|
|
41
|
+
note: {
|
|
42
|
+
id: "",
|
|
43
|
+
title: "",
|
|
44
|
+
createdAt: "",
|
|
45
|
+
status: "open",
|
|
46
|
+
related: [],
|
|
47
|
+
body: "",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
exitCode: 1,
|
|
51
|
+
summary: "exploration.show requires --id <slug>",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fileName = `${slug}.md`;
|
|
56
|
+
const result = await readAndParseExplorationNote(explorationsDirPath, fileName);
|
|
57
|
+
if (!result) {
|
|
58
|
+
if (outputFormat === "pretty") {
|
|
59
|
+
logger.info(`Exploration note not found: ${slug}`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
data: {
|
|
63
|
+
command: "exploration.show",
|
|
64
|
+
status: "error",
|
|
65
|
+
note: {
|
|
66
|
+
id: slug,
|
|
67
|
+
title: "",
|
|
68
|
+
createdAt: "",
|
|
69
|
+
status: "open",
|
|
70
|
+
related: [],
|
|
71
|
+
body: "",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
exitCode: 1,
|
|
75
|
+
summary: `Exploration note not found: ${slug}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const fm = result.parsed.frontmatter;
|
|
80
|
+
const note: ExplorationNote = {
|
|
81
|
+
id: String(fm["id"] ?? slug),
|
|
82
|
+
title: String(fm["title"] ?? ""),
|
|
83
|
+
createdAt: String(fm["createdAt"] ?? ""),
|
|
84
|
+
status: String(fm["status"] ?? "open") as ExplorationStatus,
|
|
85
|
+
related: parseRelatedArray(fm["related"]),
|
|
86
|
+
body: result.parsed.body,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (outputFormat === "pretty") {
|
|
90
|
+
logger.section(`Exploration: ${note.title}`);
|
|
91
|
+
logger.info(`ID: ${note.id} Status: ${note.status} Created: ${note.createdAt}`);
|
|
92
|
+
if (note.related.length > 0) {
|
|
93
|
+
logger.info(`Related: ${note.related.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
logger.info("");
|
|
96
|
+
logger.info(note.body.trim());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
data: {
|
|
101
|
+
command: "exploration.show",
|
|
102
|
+
status: "ok",
|
|
103
|
+
note,
|
|
104
|
+
},
|
|
105
|
+
summary: `Exploration note: ${note.id}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Barrel export for the exploration domain — types, constants, module, and handlers.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement exploration logic here; delegate to handlers/ and exploration.module.ts.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0710: expose forgeExplorationModule and exploration types from the exploration domain.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { forgeExplorationModule } from "./exploration.module.ts";
|
|
14
|
+
export { runExplorationList } from "./handlers/list.ts";
|
|
15
|
+
export { runExplorationShow } from "./handlers/show.ts";
|
|
16
|
+
export { runExplorationArchive } from "./handlers/archive.ts";
|
|
17
|
+
export type {
|
|
18
|
+
ExplorationStatus,
|
|
19
|
+
ExplorationNote,
|
|
20
|
+
ExplorationListEntry,
|
|
21
|
+
ExplorationListResult,
|
|
22
|
+
ExplorationShowResult,
|
|
23
|
+
ExplorationArchiveResult,
|
|
24
|
+
} from "./types.ts";
|
|
25
|
+
export { EXPLORATION_STATUSES, EXPLORATION_DIR, EXPLORATION_SLUG_PATTERN } from "./types.ts";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
Defines types and constants for managing exploration notes: statuses,
|
|
5
|
+
frontmatter shape, list/show/archive result types.
|
|
6
|
+
</purpose>
|
|
7
|
+
<non-goals>
|
|
8
|
+
<item>Do not implement exploration processing logic here.</item>
|
|
9
|
+
<item>Do not handle user input or command execution.</item>
|
|
10
|
+
</non-goals>
|
|
11
|
+
</MODULE_CONTRACT>
|
|
12
|
+
<CHANGE_SUMMARY>
|
|
13
|
+
<item>RFC-0710: initial exploration types and constants.</item>
|
|
14
|
+
</CHANGE_SUMMARY>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type ExplorationStatus = "open" | "explored" | "archived";
|
|
18
|
+
|
|
19
|
+
export const EXPLORATION_STATUSES: readonly ExplorationStatus[] = [
|
|
20
|
+
"open",
|
|
21
|
+
"explored",
|
|
22
|
+
"archived",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export interface ExplorationNote {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
status: ExplorationStatus;
|
|
30
|
+
related: string[];
|
|
31
|
+
body: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ExplorationListEntry {
|
|
35
|
+
id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
status: ExplorationStatus;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ExplorationListResult {
|
|
42
|
+
command: "exploration.list";
|
|
43
|
+
status: "ok";
|
|
44
|
+
count: number;
|
|
45
|
+
explorations: ExplorationListEntry[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ExplorationShowResult {
|
|
49
|
+
command: "exploration.show";
|
|
50
|
+
status: "ok" | "error";
|
|
51
|
+
note: ExplorationNote;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ExplorationArchiveResult {
|
|
55
|
+
command: "exploration.archive";
|
|
56
|
+
status: "ok" | "error";
|
|
57
|
+
id: string;
|
|
58
|
+
previousStatus: string;
|
|
59
|
+
newStatus: "archived";
|
|
60
|
+
related: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const EXPLORATION_DIR = "docs/explorations";
|
|
64
|
+
|
|
65
|
+
export const EXPLORATION_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|