@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,348 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
session.save handler — converts raw ATIF files from docs/sessions/.raw/ to
|
|
5
|
+
structured markdown in docs/sessions/ with auto-extracted metadata.
|
|
6
|
+
Deterministic only — no LLM/intelligent annotation (that is fo-session-save skill).
|
|
7
|
+
</purpose>
|
|
8
|
+
<non-goals>
|
|
9
|
+
<item>Does not generate semantic summaries or decisions — that is the fo-session-save skill.</item>
|
|
10
|
+
<item>Does not archive sessions — that is session.archive.</item>
|
|
11
|
+
</non-goals>
|
|
12
|
+
</MODULE_CONTRACT>
|
|
13
|
+
<CHANGE_SUMMARY>
|
|
14
|
+
<item>RFC-0537: implement session.save command handler.</item>
|
|
15
|
+
<item>Replace fs.unlink with trashPath for raw file deletion (trash bin for LLM-initiated deletions).</item>
|
|
16
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from "node:fs/promises";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import YAML from "yaml";
|
|
23
|
+
import type {
|
|
24
|
+
ForgeCommandInput,
|
|
25
|
+
ForgeCommandResult,
|
|
26
|
+
ForgeRuntimeContext,
|
|
27
|
+
} from "../../../src/types.ts";
|
|
28
|
+
import { parseAtif, messagesToTranscriptMarkdown } from "../atif-parser.ts";
|
|
29
|
+
import { trashPath } from "../../../src/utils/fs-trash.ts";
|
|
30
|
+
import {
|
|
31
|
+
SESSION_DIR,
|
|
32
|
+
SESSION_RAW_SUBDIR,
|
|
33
|
+
type SessionType,
|
|
34
|
+
type SessionSaveResult,
|
|
35
|
+
type SessionSaveSkip,
|
|
36
|
+
} from "../types.ts";
|
|
37
|
+
|
|
38
|
+
const RFC_PATTERN = /\bRFC-\d{4}\b/g;
|
|
39
|
+
const FILE_PATH_PATTERN = /\b(?:packages|docs|services|systems|tools|scripts)\/[^\s"'`<>|]+/g;
|
|
40
|
+
const COMMIT_HASH_PATTERN = /\b[0-9a-f]{7,40}\b/g;
|
|
41
|
+
const COMMAND_PATTERN =
|
|
42
|
+
/\b(?:session|rfc|adr|plan|audit|docs|mission|forge|compass|werkstatt|spec|naming|workflow|bordbuch|sternsystem|release)\.\w+/g;
|
|
43
|
+
|
|
44
|
+
const TYPE_INDICATORS: Array<{ type: SessionType; patterns: RegExp[] }> = [
|
|
45
|
+
{ type: "grilling", patterns: [/grilling/i, /\/grilling/i, /fo-idea.*plan/i] },
|
|
46
|
+
{
|
|
47
|
+
type: "mission",
|
|
48
|
+
patterns: [/mission\.open/i, /mission\.materialize/i, /mission\.reconcile/i, /mission\.close/i],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "implementation",
|
|
52
|
+
patterns: [/rfc\.implement\.stamp/i, /implement:\s/i, /fo-idea-implement/i],
|
|
53
|
+
},
|
|
54
|
+
{ type: "review", patterns: [/fo-review/i, /review:\s/i] },
|
|
55
|
+
{ type: "fix", patterns: [/fo-fix/i, /fix:\s/i] },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
function extractRfcIds(content: string): string[] {
|
|
59
|
+
const matches = content.match(RFC_PATTERN);
|
|
60
|
+
return matches ? [...new Set(matches)] : [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function extractFilePaths(content: string): string[] {
|
|
64
|
+
const matches = content.match(FILE_PATH_PATTERN);
|
|
65
|
+
return matches ? [...new Set(matches)] : [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function extractCommitHashes(content: string): string[] {
|
|
69
|
+
const matches = content.match(COMMIT_HASH_PATTERN);
|
|
70
|
+
if (!matches) return [];
|
|
71
|
+
// Filter to plausible git hashes (7-40 hex chars, but exclude pure version numbers)
|
|
72
|
+
const filtered = matches.filter((h) => {
|
|
73
|
+
// Exclude strings that look like version numbers (e.g. 1.0.0)
|
|
74
|
+
if (/^\d+$/.test(h) && h.length <= 8) return false;
|
|
75
|
+
return true;
|
|
76
|
+
});
|
|
77
|
+
return [...new Set(filtered)];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractCommands(content: string): string[] {
|
|
81
|
+
const matches = content.match(COMMAND_PATTERN);
|
|
82
|
+
return matches ? [...new Set(matches)] : [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function detectSessionTypes(content: string): SessionType[] {
|
|
86
|
+
const detected: SessionType[] = [];
|
|
87
|
+
for (const { type, patterns } of TYPE_INDICATORS) {
|
|
88
|
+
if (patterns.some((p) => p.test(content))) {
|
|
89
|
+
detected.push(type);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (detected.length === 0) {
|
|
93
|
+
return ["freeform"];
|
|
94
|
+
}
|
|
95
|
+
return detected;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function computeSessionId(rawContent: string, timestamp: Date): string {
|
|
99
|
+
const hash = createHash("sha256").update(rawContent).digest("hex").slice(0, 6);
|
|
100
|
+
const parts = [
|
|
101
|
+
timestamp.getFullYear(),
|
|
102
|
+
String(timestamp.getMonth() + 1).padStart(2, "0"),
|
|
103
|
+
String(timestamp.getDate()).padStart(2, "0"),
|
|
104
|
+
String(timestamp.getHours()).padStart(2, "0"),
|
|
105
|
+
String(timestamp.getMinutes()).padStart(2, "0"),
|
|
106
|
+
String(timestamp.getSeconds()).padStart(2, "0"),
|
|
107
|
+
];
|
|
108
|
+
return `${parts.join("-")}-${hash}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function extractTimestampFromContent(content: string): Date | null {
|
|
112
|
+
// Try to find an ISO 8601 timestamp in the content
|
|
113
|
+
const isoMatch = content.match(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:?\d{2}|Z)?/);
|
|
114
|
+
if (isoMatch) {
|
|
115
|
+
const date = new Date(isoMatch[0].replace(" ", "T"));
|
|
116
|
+
if (!isNaN(date.getTime())) return date;
|
|
117
|
+
}
|
|
118
|
+
// Try date-only
|
|
119
|
+
const dateMatch = content.match(/\d{4}-\d{2}-\d{2}/);
|
|
120
|
+
if (dateMatch) {
|
|
121
|
+
const date = new Date(dateMatch[0] + "T00:00:00");
|
|
122
|
+
if (!isNaN(date.getTime())) return date;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildSessionMarkdown(
|
|
128
|
+
id: string,
|
|
129
|
+
timestamp: Date,
|
|
130
|
+
types: SessionType[],
|
|
131
|
+
metadata: {
|
|
132
|
+
relatedRfcs: string[];
|
|
133
|
+
relatedArtifacts: string[];
|
|
134
|
+
commits: string[];
|
|
135
|
+
files: string[];
|
|
136
|
+
commands: string[];
|
|
137
|
+
},
|
|
138
|
+
transcriptMarkdown: string,
|
|
139
|
+
): string {
|
|
140
|
+
const isoDate = timestamp.toISOString();
|
|
141
|
+
const frontmatter: Record<string, unknown> = {
|
|
142
|
+
id,
|
|
143
|
+
date: isoDate,
|
|
144
|
+
duration: null,
|
|
145
|
+
types,
|
|
146
|
+
summary: "",
|
|
147
|
+
relatedRfcs: metadata.relatedRfcs,
|
|
148
|
+
relatedArtifacts: metadata.relatedArtifacts,
|
|
149
|
+
decisions: [],
|
|
150
|
+
commits: metadata.commits,
|
|
151
|
+
files: metadata.files,
|
|
152
|
+
commands: metadata.commands,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const yamlStr = YAML.stringify(frontmatter, { lineWidth: 0 });
|
|
156
|
+
return `---\n${yamlStr}---\n\n# Session: ${id}\n\n## Transcript\n\n${transcriptMarkdown}\n`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function runSessionSave(
|
|
160
|
+
input: ForgeCommandInput,
|
|
161
|
+
context: ForgeRuntimeContext,
|
|
162
|
+
): Promise<ForgeCommandResult<SessionSaveResult & { skipped: SessionSaveSkip[] }>> {
|
|
163
|
+
const { workspaceRoot, logger, outputFormat } = context;
|
|
164
|
+
const sessionDirPath = path.join(workspaceRoot, SESSION_DIR);
|
|
165
|
+
const rawDirPath = path.join(sessionDirPath, SESSION_RAW_SUBDIR);
|
|
166
|
+
|
|
167
|
+
const dryRun = context.dryRun || input.flags["dry-run"] === true;
|
|
168
|
+
const keepRaw = input.flags["keep-raw"] === true;
|
|
169
|
+
const rawFileFlag = input.flags["raw-file"] as string | undefined;
|
|
170
|
+
|
|
171
|
+
// Determine which raw files to process
|
|
172
|
+
let rawFiles: string[];
|
|
173
|
+
if (rawFileFlag) {
|
|
174
|
+
const resolvedPath = path.isAbsolute(rawFileFlag)
|
|
175
|
+
? rawFileFlag
|
|
176
|
+
: path.join(workspaceRoot, rawFileFlag);
|
|
177
|
+
try {
|
|
178
|
+
await fs.access(resolvedPath);
|
|
179
|
+
} catch {
|
|
180
|
+
throw new Error(`Raw file not found: ${rawFileFlag}`);
|
|
181
|
+
}
|
|
182
|
+
rawFiles = [
|
|
183
|
+
path.isAbsolute(rawFileFlag)
|
|
184
|
+
? path.relative(rawDirPath, resolvedPath)
|
|
185
|
+
: rawFileFlag.replace(`${SESSION_DIR}/${SESSION_RAW_SUBDIR}/`, ""),
|
|
186
|
+
];
|
|
187
|
+
} else {
|
|
188
|
+
try {
|
|
189
|
+
const entries = await fs.readdir(rawDirPath, { withFileTypes: true });
|
|
190
|
+
rawFiles = entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
191
|
+
} catch {
|
|
192
|
+
rawFiles = [];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (rawFiles.length === 0) {
|
|
197
|
+
if (outputFormat === "pretty") {
|
|
198
|
+
logger.info("No raw files to process.");
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
data: {
|
|
202
|
+
command: "session.save",
|
|
203
|
+
status: "ok",
|
|
204
|
+
file: "",
|
|
205
|
+
rawFile: "",
|
|
206
|
+
rawDeleted: false,
|
|
207
|
+
id: "",
|
|
208
|
+
types: [],
|
|
209
|
+
extractedMetadata: {
|
|
210
|
+
relatedRfcs: [],
|
|
211
|
+
relatedArtifacts: [],
|
|
212
|
+
commits: [],
|
|
213
|
+
files: [],
|
|
214
|
+
commands: [],
|
|
215
|
+
},
|
|
216
|
+
dryRun,
|
|
217
|
+
skipped: [],
|
|
218
|
+
},
|
|
219
|
+
summary: "No raw files to process",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const saved: SessionSaveResult[] = [];
|
|
224
|
+
const skipped: SessionSaveSkip[] = [];
|
|
225
|
+
|
|
226
|
+
for (const rawFileName of rawFiles) {
|
|
227
|
+
const rawFilePath = path.join(rawDirPath, rawFileName);
|
|
228
|
+
let rawContent: string;
|
|
229
|
+
try {
|
|
230
|
+
rawContent = await fs.readFile(rawFilePath, "utf-8");
|
|
231
|
+
} catch (err) {
|
|
232
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
233
|
+
skipped.push({ rawFile: rawFileName, reason: "already processed by another process" });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Parse ATIF
|
|
240
|
+
const atifResult = parseAtif(rawContent);
|
|
241
|
+
const transcriptText = messagesToTranscriptMarkdown(atifResult.messages);
|
|
242
|
+
|
|
243
|
+
// Extract metadata
|
|
244
|
+
const fullContent = rawContent;
|
|
245
|
+
const relatedRfcs = extractRfcIds(fullContent);
|
|
246
|
+
const relatedArtifacts = extractFilePaths(fullContent);
|
|
247
|
+
const commits = extractCommitHashes(fullContent);
|
|
248
|
+
const files = extractFilePaths(fullContent);
|
|
249
|
+
const commands = extractCommands(fullContent);
|
|
250
|
+
const types = detectSessionTypes(fullContent);
|
|
251
|
+
|
|
252
|
+
// Compute session id
|
|
253
|
+
const extractedTimestamp = extractTimestampFromContent(fullContent);
|
|
254
|
+
const timestamp = extractedTimestamp ?? new Date();
|
|
255
|
+
const id = computeSessionId(rawContent, timestamp);
|
|
256
|
+
|
|
257
|
+
// Check if output already exists (idempotency)
|
|
258
|
+
const outputFileName = `${id}.md`;
|
|
259
|
+
const outputPath = path.join(sessionDirPath, outputFileName);
|
|
260
|
+
const outputRel = path.join(SESSION_DIR, outputFileName);
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
await fs.access(outputPath);
|
|
264
|
+
skipped.push({ rawFile: rawFileName, reason: "already converted" });
|
|
265
|
+
if (outputFormat === "pretty") {
|
|
266
|
+
logger.info(`Skipped ${rawFileName}: already converted to ${outputRel}`);
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
} catch {
|
|
270
|
+
// Output doesn't exist — proceed
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Build markdown
|
|
274
|
+
const markdown = buildSessionMarkdown(
|
|
275
|
+
id,
|
|
276
|
+
timestamp,
|
|
277
|
+
types,
|
|
278
|
+
{ relatedRfcs, relatedArtifacts, commits, files, commands },
|
|
279
|
+
transcriptText,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
if (!dryRun) {
|
|
283
|
+
await fs.mkdir(sessionDirPath, { recursive: true });
|
|
284
|
+
await fs.writeFile(outputPath, markdown, "utf-8");
|
|
285
|
+
|
|
286
|
+
// Delete raw file unless --keep-raw
|
|
287
|
+
if (!keepRaw) {
|
|
288
|
+
try {
|
|
289
|
+
await trashPath(rawFilePath);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const result: SessionSaveResult = {
|
|
299
|
+
command: "session.save",
|
|
300
|
+
status: "ok",
|
|
301
|
+
file: outputRel,
|
|
302
|
+
rawFile: path.join(SESSION_DIR, SESSION_RAW_SUBDIR, rawFileName),
|
|
303
|
+
rawDeleted: !dryRun && !keepRaw,
|
|
304
|
+
id,
|
|
305
|
+
types,
|
|
306
|
+
extractedMetadata: {
|
|
307
|
+
relatedRfcs,
|
|
308
|
+
relatedArtifacts,
|
|
309
|
+
commits,
|
|
310
|
+
files,
|
|
311
|
+
commands,
|
|
312
|
+
},
|
|
313
|
+
dryRun,
|
|
314
|
+
};
|
|
315
|
+
saved.push(result);
|
|
316
|
+
|
|
317
|
+
if (outputFormat === "pretty") {
|
|
318
|
+
logger.success(`Saved session ${id} to ${outputRel}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const firstSaved = saved[0];
|
|
323
|
+
return {
|
|
324
|
+
data: firstSaved
|
|
325
|
+
? { ...firstSaved, skipped }
|
|
326
|
+
: {
|
|
327
|
+
command: "session.save" as const,
|
|
328
|
+
status: "ok" as const,
|
|
329
|
+
file: "",
|
|
330
|
+
rawFile: "",
|
|
331
|
+
rawDeleted: false,
|
|
332
|
+
id: "",
|
|
333
|
+
types: [],
|
|
334
|
+
extractedMetadata: {
|
|
335
|
+
relatedRfcs: [],
|
|
336
|
+
relatedArtifacts: [],
|
|
337
|
+
commits: [],
|
|
338
|
+
files: [],
|
|
339
|
+
commands: [],
|
|
340
|
+
},
|
|
341
|
+
dryRun,
|
|
342
|
+
skipped,
|
|
343
|
+
},
|
|
344
|
+
summary: dryRun
|
|
345
|
+
? `[dry-run] Would save ${saved.length} session(s), skip ${skipped.length}`
|
|
346
|
+
: `Saved ${saved.length} session(s), skipped ${skipped.length}`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
session.validate handler — checks session frontmatter schema (SES-01),
|
|
5
|
+
id-filename match (SES-02), RFC-id existence (SES-03), raw file hygiene
|
|
6
|
+
(SES-04), non-markdown file detection (SES-05), and missing checkpoint
|
|
7
|
+
fields in implementation/mission sessions (SES-06, RFC-0884).
|
|
8
|
+
</purpose>
|
|
9
|
+
<non-goals>
|
|
10
|
+
<item>Does not move or modify files — that is session.archive.</item>
|
|
11
|
+
<item>Does not check transcript content quality — that is the fo-session-save skill.</item>
|
|
12
|
+
</non-goals>
|
|
13
|
+
</MODULE_CONTRACT>
|
|
14
|
+
<CHANGE_SUMMARY>
|
|
15
|
+
<item>RFC-0537: implement session.validate command handler.</item>
|
|
16
|
+
<item>RFC-0884: add SES-06 warning for missing checkpoint fields in implementation/mission sessions.</item>
|
|
17
|
+
</CHANGE_SUMMARY>
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import {
|
|
22
|
+
listSessionFiles,
|
|
23
|
+
listNonMarkdownSessionFiles,
|
|
24
|
+
listRawFiles,
|
|
25
|
+
readAndParseSession,
|
|
26
|
+
} from "../frontmatter-io.ts";
|
|
27
|
+
import { listRfcFiles, readAndParseRfc } from "../../rfc/frontmatter-io.ts";
|
|
28
|
+
import type {
|
|
29
|
+
ForgeCommandInput,
|
|
30
|
+
ForgeCommandResult,
|
|
31
|
+
ForgeRuntimeContext,
|
|
32
|
+
} from "../../../src/types.ts";
|
|
33
|
+
import {
|
|
34
|
+
SESSION_DIR,
|
|
35
|
+
SESSION_KNOWN_KEYS,
|
|
36
|
+
SESSION_REQUIRED_KEYS,
|
|
37
|
+
SESSION_TYPES,
|
|
38
|
+
SES_RULES,
|
|
39
|
+
type SessionValidationResult,
|
|
40
|
+
type SessionValidationViolation,
|
|
41
|
+
} from "../types.ts";
|
|
42
|
+
|
|
43
|
+
const RFC_DIR = "docs/rfcs";
|
|
44
|
+
|
|
45
|
+
async function loadRfcIds(workspaceRoot: string): Promise<Set<string>> {
|
|
46
|
+
const rfcIds = new Set<string>();
|
|
47
|
+
const rfcDirPath = path.join(workspaceRoot, RFC_DIR);
|
|
48
|
+
const files = await listRfcFiles(rfcDirPath);
|
|
49
|
+
for (const fileName of files) {
|
|
50
|
+
const result = await readAndParseRfc(rfcDirPath, fileName);
|
|
51
|
+
const id = result && "parsed" in result ? String(result.parsed.frontmatter["id"] ?? "") : "";
|
|
52
|
+
if (id) rfcIds.add(id);
|
|
53
|
+
}
|
|
54
|
+
return rfcIds;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function runSessionValidate(
|
|
58
|
+
input: ForgeCommandInput,
|
|
59
|
+
context: ForgeRuntimeContext,
|
|
60
|
+
): Promise<ForgeCommandResult<SessionValidationResult>> {
|
|
61
|
+
const { workspaceRoot, logger, outputFormat } = context;
|
|
62
|
+
const sessionDirPath = path.join(workspaceRoot, SESSION_DIR);
|
|
63
|
+
|
|
64
|
+
const targetId = input.flags["id"] as string | undefined;
|
|
65
|
+
|
|
66
|
+
const allFiles = await listSessionFiles(sessionDirPath);
|
|
67
|
+
const filesToValidate = targetId
|
|
68
|
+
? allFiles.filter((f) => path.basename(f).toLowerCase().startsWith(targetId.toLowerCase()))
|
|
69
|
+
: allFiles;
|
|
70
|
+
|
|
71
|
+
if (filesToValidate.length === 0) {
|
|
72
|
+
if (outputFormat === "pretty") {
|
|
73
|
+
logger.info("No session files found to validate.");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
data: {
|
|
77
|
+
command: "session.validate",
|
|
78
|
+
status: "pass",
|
|
79
|
+
violations: [],
|
|
80
|
+
checked: 0,
|
|
81
|
+
},
|
|
82
|
+
summary: "No session files found to validate",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const knownRfcIds = await loadRfcIds(workspaceRoot);
|
|
87
|
+
const knownKeys = new Set<string>(SESSION_KNOWN_KEYS);
|
|
88
|
+
const violations: SessionValidationViolation[] = [];
|
|
89
|
+
|
|
90
|
+
for (const fileName of filesToValidate) {
|
|
91
|
+
const result = await readAndParseSession(sessionDirPath, fileName);
|
|
92
|
+
if (!result) continue;
|
|
93
|
+
|
|
94
|
+
const fm = result.parsed.frontmatter;
|
|
95
|
+
const id = String(fm["id"] ?? "");
|
|
96
|
+
const relFile = path.join(SESSION_DIR, fileName);
|
|
97
|
+
const basename = path.basename(fileName, ".md");
|
|
98
|
+
|
|
99
|
+
// SES-01: Required frontmatter fields
|
|
100
|
+
for (const key of SESSION_REQUIRED_KEYS) {
|
|
101
|
+
if (!(key in fm) || fm[key] === undefined || fm[key] === null) {
|
|
102
|
+
violations.push({
|
|
103
|
+
rule: SES_RULES.SES_01,
|
|
104
|
+
file: relFile,
|
|
105
|
+
message: `Required field "${key}" is missing`,
|
|
106
|
+
severity: "error",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// SES-01: Check types is a valid array
|
|
112
|
+
// typesArray is also used by SES-06 below — hoisted here to make the coupling explicit.
|
|
113
|
+
const typesValue = fm["types"];
|
|
114
|
+
const typesArray = Array.isArray(typesValue) ? (typesValue as string[]) : [];
|
|
115
|
+
if (Array.isArray(typesValue)) {
|
|
116
|
+
for (const t of typesValue) {
|
|
117
|
+
if (!SESSION_TYPES.includes(t as never)) {
|
|
118
|
+
violations.push({
|
|
119
|
+
rule: SES_RULES.SES_01,
|
|
120
|
+
file: relFile,
|
|
121
|
+
message: `Invalid session type "${t}". Must be one of: ${SESSION_TYPES.join(", ")}`,
|
|
122
|
+
severity: "error",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else if (typesValue !== undefined) {
|
|
127
|
+
violations.push({
|
|
128
|
+
rule: SES_RULES.SES_01,
|
|
129
|
+
file: relFile,
|
|
130
|
+
message: `Field "types" must be an array`,
|
|
131
|
+
severity: "error",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// SES-01: Warn on unknown keys
|
|
136
|
+
for (const key of Object.keys(fm)) {
|
|
137
|
+
if (!knownKeys.has(key)) {
|
|
138
|
+
violations.push({
|
|
139
|
+
rule: SES_RULES.SES_01,
|
|
140
|
+
file: relFile,
|
|
141
|
+
message: `Unknown frontmatter key "${key}"`,
|
|
142
|
+
severity: "warning",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// SES-02: id matches filename
|
|
148
|
+
if (id && id !== basename) {
|
|
149
|
+
violations.push({
|
|
150
|
+
rule: SES_RULES.SES_02,
|
|
151
|
+
file: relFile,
|
|
152
|
+
message: `id "${id}" does not match filename "${basename}"`,
|
|
153
|
+
severity: "error",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// SES-03: relatedRfcs reference existing RFCs
|
|
158
|
+
const relatedRfcs = Array.isArray(fm["relatedRfcs"]) ? (fm["relatedRfcs"] as string[]) : [];
|
|
159
|
+
for (const rfcId of relatedRfcs) {
|
|
160
|
+
if (!knownRfcIds.has(rfcId)) {
|
|
161
|
+
violations.push({
|
|
162
|
+
rule: SES_RULES.SES_03,
|
|
163
|
+
file: relFile,
|
|
164
|
+
message: `relatedRfcs references non-existent RFC "${rfcId}"`,
|
|
165
|
+
severity: "error",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// SES-06: Missing checkpoint fields in implementation/mission sessions
|
|
171
|
+
// typesArray is extracted by SES-01 above.
|
|
172
|
+
const isImplOrMission = typesArray.includes("implementation") || typesArray.includes("mission");
|
|
173
|
+
if (isImplOrMission) {
|
|
174
|
+
const hasCheckpointFields =
|
|
175
|
+
"systemDelta" in fm ||
|
|
176
|
+
"diagrams" in fm ||
|
|
177
|
+
"evidence" in fm ||
|
|
178
|
+
"remainingIssues" in fm ||
|
|
179
|
+
"checkpoint" in fm;
|
|
180
|
+
if (!hasCheckpointFields) {
|
|
181
|
+
violations.push({
|
|
182
|
+
rule: SES_RULES.SES_06,
|
|
183
|
+
file: relFile,
|
|
184
|
+
message:
|
|
185
|
+
"Implementation/mission session missing checkpoint frontmatter fields (systemDelta, diagrams, evidence, remainingIssues, checkpoint). Agent should populate these during fo-session-save.",
|
|
186
|
+
severity: "warning",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// SES-04: Raw files in .raw/ that haven't been processed (warning)
|
|
193
|
+
const rawFiles = await listRawFiles(sessionDirPath);
|
|
194
|
+
for (const rawFile of rawFiles) {
|
|
195
|
+
violations.push({
|
|
196
|
+
rule: SES_RULES.SES_04,
|
|
197
|
+
file: path.join(SESSION_DIR, ".raw", rawFile),
|
|
198
|
+
message: `Unprocessed raw file in .raw/ — run session.save to convert`,
|
|
199
|
+
severity: "warning",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// SES-05: Non-markdown files in docs/sessions/ (excluding .raw/ and archive/)
|
|
204
|
+
const nonMdFiles = await listNonMarkdownSessionFiles(sessionDirPath);
|
|
205
|
+
for (const nonMdFile of nonMdFiles) {
|
|
206
|
+
violations.push({
|
|
207
|
+
rule: SES_RULES.SES_05,
|
|
208
|
+
file: path.join(SESSION_DIR, nonMdFile),
|
|
209
|
+
message: `Non-markdown file in docs/sessions/ — only .md files are allowed (excluding .raw/ and archive/)`,
|
|
210
|
+
severity: "error",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const hasErrors = violations.some((v) => v.severity === "error");
|
|
215
|
+
const resultStatus = hasErrors ? "fail" : "pass";
|
|
216
|
+
|
|
217
|
+
if (outputFormat === "pretty") {
|
|
218
|
+
if (violations.length === 0) {
|
|
219
|
+
logger.success(`All ${filesToValidate.length} session file(s) passed validation.`);
|
|
220
|
+
} else {
|
|
221
|
+
logger.section(`Session Validation (${filesToValidate.length} file(s))`);
|
|
222
|
+
for (const v of violations) {
|
|
223
|
+
const prefix = v.severity === "error" ? "ERROR" : "WARN";
|
|
224
|
+
logger[v.severity === "error" ? "error" : "warn"](
|
|
225
|
+
`[${prefix}] ${v.rule}: ${v.file}: ${v.message}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
229
|
+
const warnCount = violations.filter((v) => v.severity === "warning").length;
|
|
230
|
+
if (errorCount > 0) {
|
|
231
|
+
logger.error(`${errorCount} error(s), ${warnCount} warning(s)`);
|
|
232
|
+
} else {
|
|
233
|
+
logger.warn(`${warnCount} warning(s), 0 errors — passed`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
data: {
|
|
240
|
+
command: "session.validate",
|
|
241
|
+
status: resultStatus,
|
|
242
|
+
violations,
|
|
243
|
+
checked: filesToValidate.length,
|
|
244
|
+
},
|
|
245
|
+
exitCode: hasErrors ? 1 : 0,
|
|
246
|
+
summary: hasErrors
|
|
247
|
+
? `${violations.filter((v) => v.severity === "error").length} error(s) found`
|
|
248
|
+
: `All ${filesToValidate.length} session file(s) passed validation`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Barrel export for the session domain — types, module, and handlers.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement session logic here; delegate to handlers/ and session.module.ts.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-0537: initial session module barrel.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { forgeSessionModule } from "./session.module.ts";
|
|
14
|
+
export { runSessionSave } from "./handlers/save.ts";
|
|
15
|
+
export { runSessionArchive } from "./handlers/archive.ts";
|
|
16
|
+
export { runSessionValidate } from "./handlers/validate.ts";
|
|
17
|
+
export { runSessionList } from "./handlers/list.ts";
|
|
18
|
+
export {
|
|
19
|
+
parseSessionFile,
|
|
20
|
+
listSessionFiles,
|
|
21
|
+
listArchivedSessionFiles,
|
|
22
|
+
readAndParseSession,
|
|
23
|
+
listNonMarkdownSessionFiles,
|
|
24
|
+
listRawFiles,
|
|
25
|
+
type ParsedSession,
|
|
26
|
+
} from "./frontmatter-io.ts";
|
|
27
|
+
export {
|
|
28
|
+
parseAtif,
|
|
29
|
+
messagesToTranscriptMarkdown,
|
|
30
|
+
type AtifMessage,
|
|
31
|
+
type AtifParseResult,
|
|
32
|
+
} from "./atif-parser.ts";
|
|
33
|
+
export {
|
|
34
|
+
SESSION_DIR,
|
|
35
|
+
SESSION_RAW_SUBDIR,
|
|
36
|
+
SESSION_ARCHIVE_SUBDIR,
|
|
37
|
+
SESSION_TYPES,
|
|
38
|
+
SES_RULES,
|
|
39
|
+
type SessionType,
|
|
40
|
+
type SessionFrontmatter,
|
|
41
|
+
type SessionSaveResult,
|
|
42
|
+
type SessionArchiveResult,
|
|
43
|
+
type SessionArchiveMove,
|
|
44
|
+
type SessionArchiveSkip,
|
|
45
|
+
type SessionValidationResult,
|
|
46
|
+
type SessionValidationViolation,
|
|
47
|
+
type SessionListResult,
|
|
48
|
+
type SessionListEntry,
|
|
49
|
+
type SesRule,
|
|
50
|
+
} from "./types.ts";
|