silvermoon 0.0.1 → 0.0.3

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.
@@ -1,109 +1,109 @@
1
- export const IDEA_TEMPLATE = `# Idea
2
-
3
- ## Intent
4
-
5
- <!-- State the desired outcome in one or two sentences. -->
6
-
7
- ## Context
8
-
9
- <!-- Describe the current problem, situation, or opportunity. -->
10
-
11
- ## Desired outcome
12
-
13
- <!-- Describe the externally meaningful state that should become true. -->
14
-
15
- ## Scope
16
-
17
- ### In scope
18
-
19
- <!-- Describe what this idea includes. -->
20
-
21
- ### Out of scope
22
-
23
- <!-- Describe adjacent work this idea intentionally excludes. -->
24
-
25
- ## Constraints
26
-
27
- <!-- Record material product, repository, compatibility, or operational constraints. -->
28
-
29
- ## Open questions
30
-
31
- <!-- Record unresolved decisions. Remove this section when none remain. -->
32
- `;
33
-
34
- export const IMPLEMENTATION_TEMPLATE = `# Implementation
35
-
36
- ## Steps
37
-
38
- <!--
39
- Give every step a stable I-Sxx identifier and a level-three heading.
40
- Describe what will change, its boundaries, and important design details.
41
- Do not use task-list checkboxes in this document.
42
- -->
43
-
44
- ### I-S01: Step title
45
-
46
- <!-- Describe this implementation step. -->
47
-
48
- ## Acceptance criteria
49
-
50
- <!--
51
- Give every criterion a stable I-ACxx identifier and a level-three heading.
52
- Describe both the observable outcome and the method that proves it.
53
- Do not create a separate validation section or use task-list checkboxes.
54
- -->
55
-
56
- ### I-AC01: Criterion title
57
-
58
- <!-- Describe the required outcome and how an Agent can prove it. -->
59
- `;
60
-
61
- export const DEPLOYMENT_TEMPLATE = `# Deployment
62
-
63
- ## Steps
64
-
65
- <!--
66
- Give every step a stable D-Sxx identifier and a level-three heading.
67
- Describe deployment or external-world verification work.
68
- Do not use task-list checkboxes in this document.
69
- -->
70
-
71
- ### D-S01: Step title
72
-
73
- <!-- Describe this deployment or external-verification step. -->
74
-
75
- ## Acceptance criteria
76
-
77
- <!--
78
- Give every criterion a stable D-ACxx identifier and a level-three heading.
79
- Describe both the observable external outcome and the method that proves it.
80
- Do not create a separate validation section or use task-list checkboxes.
81
- -->
82
-
83
- ### D-AC01: Criterion title
84
-
85
- <!-- Describe the required external outcome and how an Agent can prove it. -->
86
- `;
87
-
88
- export const LEDGER_TEMPLATE = `# Ledger
89
-
90
- ## Implementation
91
-
92
- ### Steps
93
-
94
- - [ ] **I-S01:** Step title
95
-
96
- ### Acceptance criteria
97
-
98
- - [ ] **I-AC01:** Criterion title
99
-
100
- ## Deployment
101
-
102
- ### Steps
103
-
104
- - [ ] **D-S01:** Step title
105
-
106
- ### Acceptance criteria
107
-
108
- - [ ] **D-AC01:** Criterion title
109
- `;
1
+ export const IDEA_TEMPLATE = `# Replace with a specific title for this idea
2
+
3
+ ## Intent
4
+
5
+ <!-- State the desired outcome in one or two sentences. -->
6
+
7
+ ## Context
8
+
9
+ <!-- Describe the current problem, situation, or opportunity. -->
10
+
11
+ ## Desired outcome
12
+
13
+ <!-- Describe the externally meaningful state that should become true. -->
14
+
15
+ ## Scope
16
+
17
+ ### In scope
18
+
19
+ <!-- Describe what this idea includes. -->
20
+
21
+ ### Out of scope
22
+
23
+ <!-- Describe adjacent work this idea intentionally excludes. -->
24
+
25
+ ## Constraints
26
+
27
+ <!-- Record material product, repository, compatibility, or operational constraints. -->
28
+
29
+ ## Open questions
30
+
31
+ <!-- Record unresolved decisions. Remove this section when none remain. -->
32
+ `;
33
+
34
+ export const IMPLEMENTATION_TEMPLATE = `# Implementation
35
+
36
+ ## Steps
37
+
38
+ <!--
39
+ Give every step a stable I-Sxx identifier and a level-three heading.
40
+ Describe what will change, its boundaries, and important design details.
41
+ Do not use task-list checkboxes in this document.
42
+ -->
43
+
44
+ ### I-S01: Step title
45
+
46
+ <!-- Describe this implementation step. -->
47
+
48
+ ## Acceptance criteria
49
+
50
+ <!--
51
+ Give every criterion a stable I-ACxx identifier and a level-three heading.
52
+ Describe both the observable outcome and the method that proves it.
53
+ Do not create a separate validation section or use task-list checkboxes.
54
+ -->
55
+
56
+ ### I-AC01: Criterion title
57
+
58
+ <!-- Describe the required outcome and how an Agent can prove it. -->
59
+ `;
60
+
61
+ export const DEPLOYMENT_TEMPLATE = `# Deployment
62
+
63
+ ## Steps
64
+
65
+ <!--
66
+ Give every step a stable D-Sxx identifier and a level-three heading.
67
+ Describe deployment or external-world verification work.
68
+ Do not use task-list checkboxes in this document.
69
+ -->
70
+
71
+ ### D-S01: Step title
72
+
73
+ <!-- Describe this deployment or external-verification step. -->
74
+
75
+ ## Acceptance criteria
76
+
77
+ <!--
78
+ Give every criterion a stable D-ACxx identifier and a level-three heading.
79
+ Describe both the observable external outcome and the method that proves it.
80
+ Do not create a separate validation section or use task-list checkboxes.
81
+ -->
82
+
83
+ ### D-AC01: Criterion title
84
+
85
+ <!-- Describe the required external outcome and how an Agent can prove it. -->
86
+ `;
87
+
88
+ export const LEDGER_TEMPLATE = `# Ledger
89
+
90
+ ## Implementation
91
+
92
+ ### Implementation steps
93
+
94
+ - [ ] **I-S01:** Step title
95
+
96
+ ### Implementation acceptance criteria
97
+
98
+ - [ ] **I-AC01:** Criterion title
99
+
100
+ ## Deployment
101
+
102
+ ### Deployment steps
103
+
104
+ - [ ] **D-S01:** Step title
105
+
106
+ ### Deployment acceptance criteria
107
+
108
+ - [ ] **D-AC01:** Criterion title
109
+ `;
package/src/ideas.js CHANGED
@@ -1,132 +1,141 @@
1
- import { parseStrictYaml, stringifyCanonicalYaml } from "./yaml.js";
2
-
3
- export const IDEA_STATES = [
4
- "preparing",
5
- "implementing",
6
- "deploying",
7
- "completed",
8
- "abandoned",
9
- ];
10
-
11
- const ULID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
12
- const OBJECT_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
13
- const CONTROL_CHARACTER = /[\u0000-\u001f\u007f-\u009f]/u;
14
- const STATUS_KEYS = new Set([
15
- "version",
16
- "id",
17
- "alias",
18
- "abandoned",
19
- "approvedRevision",
20
- "implementationAcceptedRevision",
21
- "deploymentAcceptedRevision",
22
- ]);
23
- const REVISION_KEYS = [
24
- "approvedRevision",
25
- "implementationAcceptedRevision",
26
- "deploymentAcceptedRevision",
27
- ];
28
-
29
- function ideaStatusError(message) {
30
- return new Error(`Invalid idea status: ${message}`);
31
- }
32
-
33
- function isMapping(value) {
34
- return value !== null && !Array.isArray(value) && typeof value === "object";
35
- }
36
-
37
- function isValidAlias(value) {
38
- return (
39
- typeof value === "string" &&
40
- value.length > 0 &&
41
- value.trim() === value &&
42
- [...value].length <= 120 &&
43
- !CONTROL_CHARACTER.test(value)
44
- );
45
- }
46
-
47
- export function isValidUlid(value) {
48
- return typeof value === "string" && ULID.test(value);
49
- }
50
-
51
- export function validateIdeaStatus(value, { objectIdLength } = {}) {
52
- if (!isMapping(value)) throw ideaStatusError("document must be a mapping");
53
- for (const key of Object.keys(value)) {
54
- if (!STATUS_KEYS.has(key)) throw ideaStatusError(`unknown field: ${key}`);
55
- }
56
- for (const key of ["version", "id"]) {
57
- if (!Object.hasOwn(value, key)) throw ideaStatusError(`missing ${key}`);
58
- }
59
- if (value.version !== 1) throw ideaStatusError("version must be 1");
60
- if (!isValidUlid(value.id)) throw ideaStatusError("id must be a canonical ULID");
61
- if (Object.hasOwn(value, "alias") && !isValidAlias(value.alias)) {
62
- throw ideaStatusError("alias must be 1 to 120 trimmed characters without controls");
63
- }
64
- if (Object.hasOwn(value, "abandoned") && value.abandoned !== true) {
65
- throw ideaStatusError("abandoned must be omitted or true");
66
- }
67
- if (
68
- objectIdLength !== undefined &&
69
- (!Number.isInteger(objectIdLength) || objectIdLength <= 0)
70
- ) {
71
- throw ideaStatusError("objectIdLength must be a positive integer");
72
- }
73
- for (const key of REVISION_KEYS) {
74
- if (!Object.hasOwn(value, key)) continue;
75
- if (
76
- typeof value[key] !== "string" ||
77
- !OBJECT_ID.test(value[key]) ||
78
- (objectIdLength !== undefined && value[key].length !== objectIdLength)
79
- ) {
80
- throw ideaStatusError(`${key} must be a lowercase hexadecimal Git object ID`);
81
- }
82
- }
83
- return value;
84
- }
85
-
86
- export function serializeIdeaStatus(value, options) {
87
- validateIdeaStatus(value, options);
88
- const canonical = {
89
- version: 1,
90
- id: value.id,
91
- };
92
- if (Object.hasOwn(value, "alias")) canonical.alias = value.alias;
93
- if (value.abandoned === true) canonical.abandoned = true;
94
- for (const key of REVISION_KEYS) {
95
- if (Object.hasOwn(value, key)) canonical[key] = value[key];
96
- }
97
- return stringifyCanonicalYaml(canonical);
98
- }
99
-
100
- export function parseIdeaStatus(source, options) {
101
- const normalizedSource = source.replaceAll("\r\n", "\n");
102
- let value;
103
- try {
104
- value = parseStrictYaml(normalizedSource);
105
- } catch (error) {
106
- throw ideaStatusError(error.message);
107
- }
108
- validateIdeaStatus(value, options);
109
- if (serializeIdeaStatus(value, options) !== normalizedSource) {
110
- throw ideaStatusError("status is not canonical");
111
- }
112
- return value;
113
- }
114
-
115
- export function deriveIdeaState(revisions, status) {
116
- if (revisions === null || Array.isArray(revisions) || typeof revisions !== "object") {
117
- throw ideaStatusError("revisions must be a mapping");
118
- }
119
- for (const key of ["idealRevision", "implementationRevision", "deploymentRevision"]) {
120
- if (typeof revisions[key] !== "string" || !OBJECT_ID.test(revisions[key])) {
121
- throw ideaStatusError(`${key} must be a lowercase hexadecimal Git object ID`);
122
- }
123
- }
124
- validateIdeaStatus(status);
125
- if (status.abandoned) return "abandoned";
126
- if (status.approvedRevision !== revisions.idealRevision) return "preparing";
127
- if (
128
- status.implementationAcceptedRevision !== revisions.implementationRevision
129
- ) return "implementing";
130
- if (status.deploymentAcceptedRevision !== revisions.deploymentRevision) return "deploying";
131
- return "completed";
1
+ import { parseStrictYaml, stringifyCanonicalYaml } from "./yaml.js";
2
+ import { isCanonicalLanguageTag } from "./language.js";
3
+
4
+ export const IDEA_STATES = [
5
+ "preparing",
6
+ "implementing",
7
+ "deploying",
8
+ "completed",
9
+ "abandoned",
10
+ ];
11
+
12
+ const ULID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
13
+ const OBJECT_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
14
+ const CONTROL_CHARACTER = /[\u0000-\u001f\u007f-\u009f]/u;
15
+ const STATUS_KEYS = new Set([
16
+ "version",
17
+ "id",
18
+ "alias",
19
+ "language",
20
+ "abandoned",
21
+ "approvedRevision",
22
+ "implementationAcceptedRevision",
23
+ "deploymentAcceptedRevision",
24
+ ]);
25
+ const REVISION_KEYS = [
26
+ "approvedRevision",
27
+ "implementationAcceptedRevision",
28
+ "deploymentAcceptedRevision",
29
+ ];
30
+
31
+ function ideaStatusError(message) {
32
+ return new Error(`Invalid idea status: ${message}`);
33
+ }
34
+
35
+ function isMapping(value) {
36
+ return value !== null && !Array.isArray(value) && typeof value === "object";
37
+ }
38
+
39
+ function isValidAlias(value) {
40
+ return (
41
+ typeof value === "string" &&
42
+ value.length > 0 &&
43
+ value.trim() === value &&
44
+ [...value].length <= 120 &&
45
+ !CONTROL_CHARACTER.test(value)
46
+ );
47
+ }
48
+
49
+ export function isValidUlid(value) {
50
+ return typeof value === "string" && ULID.test(value);
51
+ }
52
+
53
+ export function validateIdeaStatus(value, { objectIdLength } = {}) {
54
+ if (!isMapping(value)) throw ideaStatusError("document must be a mapping");
55
+ for (const key of Object.keys(value)) {
56
+ if (!STATUS_KEYS.has(key)) throw ideaStatusError(`unknown field: ${key}`);
57
+ }
58
+ for (const key of ["version", "id"]) {
59
+ if (!Object.hasOwn(value, key)) throw ideaStatusError(`missing ${key}`);
60
+ }
61
+ if (value.version !== 1) throw ideaStatusError("version must be 1");
62
+ if (!isValidUlid(value.id)) throw ideaStatusError("id must be a canonical ULID");
63
+ if (Object.hasOwn(value, "alias") && !isValidAlias(value.alias)) {
64
+ throw ideaStatusError("alias must be 1 to 120 trimmed characters without controls");
65
+ }
66
+ if (
67
+ Object.hasOwn(value, "language") &&
68
+ !isCanonicalLanguageTag(value.language)
69
+ ) {
70
+ throw ideaStatusError("language must be a canonical BCP 47 language tag");
71
+ }
72
+ if (Object.hasOwn(value, "abandoned") && value.abandoned !== true) {
73
+ throw ideaStatusError("abandoned must be omitted or true");
74
+ }
75
+ if (
76
+ objectIdLength !== undefined &&
77
+ (!Number.isInteger(objectIdLength) || objectIdLength <= 0)
78
+ ) {
79
+ throw ideaStatusError("objectIdLength must be a positive integer");
80
+ }
81
+ for (const key of REVISION_KEYS) {
82
+ if (!Object.hasOwn(value, key)) continue;
83
+ if (
84
+ typeof value[key] !== "string" ||
85
+ !OBJECT_ID.test(value[key]) ||
86
+ (objectIdLength !== undefined && value[key].length !== objectIdLength)
87
+ ) {
88
+ throw ideaStatusError(`${key} must be a lowercase hexadecimal Git object ID`);
89
+ }
90
+ }
91
+ return value;
92
+ }
93
+
94
+ export function serializeIdeaStatus(value, options) {
95
+ validateIdeaStatus(value, options);
96
+ const canonical = {
97
+ version: 1,
98
+ id: value.id,
99
+ };
100
+ if (Object.hasOwn(value, "alias")) canonical.alias = value.alias;
101
+ if (Object.hasOwn(value, "language")) canonical.language = value.language;
102
+ if (value.abandoned === true) canonical.abandoned = true;
103
+ for (const key of REVISION_KEYS) {
104
+ if (Object.hasOwn(value, key)) canonical[key] = value[key];
105
+ }
106
+ return stringifyCanonicalYaml(canonical);
107
+ }
108
+
109
+ export function parseIdeaStatus(source, options) {
110
+ const normalizedSource = source.replaceAll("\r\n", "\n");
111
+ let value;
112
+ try {
113
+ value = parseStrictYaml(normalizedSource);
114
+ } catch (error) {
115
+ throw ideaStatusError(error.message);
116
+ }
117
+ validateIdeaStatus(value, options);
118
+ if (serializeIdeaStatus(value, options) !== normalizedSource) {
119
+ throw ideaStatusError("status is not canonical");
120
+ }
121
+ return value;
122
+ }
123
+
124
+ export function deriveIdeaState(revisions, status) {
125
+ if (revisions === null || Array.isArray(revisions) || typeof revisions !== "object") {
126
+ throw ideaStatusError("revisions must be a mapping");
127
+ }
128
+ for (const key of ["idealRevision", "implementationRevision", "deploymentRevision"]) {
129
+ if (typeof revisions[key] !== "string" || !OBJECT_ID.test(revisions[key])) {
130
+ throw ideaStatusError(`${key} must be a lowercase hexadecimal Git object ID`);
131
+ }
132
+ }
133
+ validateIdeaStatus(status);
134
+ if (status.abandoned) return "abandoned";
135
+ if (status.approvedRevision !== revisions.idealRevision) return "preparing";
136
+ if (
137
+ status.implementationAcceptedRevision !== revisions.implementationRevision
138
+ ) return "implementing";
139
+ if (status.deploymentAcceptedRevision !== revisions.deploymentRevision) return "deploying";
140
+ return "completed";
132
141
  }