supipowers 2.1.0 → 2.2.1
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/README.md +71 -12
- package/package.json +4 -8
- package/skills/ui-design/SKILL.md +2 -2
- package/src/ai/final-message.ts +15 -1
- package/src/ai/schema-text.ts +60 -40
- package/src/ai/schema-validation.ts +88 -0
- package/src/ai/structured-output.ts +19 -19
- package/src/bootstrap.ts +3 -0
- package/src/commands/fix-pr.ts +166 -26
- package/src/commands/optimize-context.ts +153 -16
- package/src/commands/runbook.ts +511 -0
- package/src/config/schema.ts +102 -139
- package/src/context/rule-renderer.ts +274 -2
- package/src/context/runbook-extension-template.ts +193 -0
- package/src/context/startup-check.ts +197 -2
- package/src/context/startup-optimizer.ts +133 -10
- package/src/docs/contracts.ts +13 -23
- package/src/fix-pr/assessment.ts +63 -24
- package/src/fix-pr/contracts.ts +15 -23
- package/src/fix-pr/fetch-comments.ts +119 -0
- package/src/fix-pr/prompt-builder.ts +19 -8
- package/src/git/commit-contract.ts +13 -19
- package/src/git/commit.ts +168 -6
- package/src/harness/command.ts +98 -6
- package/src/harness/git-verification.ts +515 -0
- package/src/harness/git-verify-qa.ts +406 -0
- package/src/harness/pipeline.ts +17 -8
- package/src/harness/stages/implement-apply.ts +61 -4
- package/src/harness/stages/validate.ts +108 -0
- package/src/lsp/capabilities.ts +9 -12
- package/src/lsp/contracts.ts +15 -23
- package/src/planning/planning-ask-tool.ts +13 -2
- package/src/planning/spec.ts +21 -27
- package/src/planning/system-prompt.ts +1 -1
- package/src/planning/validate.ts +4 -7
- package/src/platform/progress.ts +11 -0
- package/src/quality/contracts.ts +15 -23
- package/src/quality/schemas.ts +40 -67
- package/src/release/contracts.ts +19 -28
- package/src/review/types.ts +142 -186
- package/src/types.ts +45 -2
- package/src/ui-design/session.ts +13 -2
- package/src/ui-design/system-prompt.ts +2 -2
- package/src/ultraplan/contracts.ts +458 -524
package/src/config/schema.ts
CHANGED
|
@@ -1,176 +1,142 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { Value } from "@sinclair/typebox/value";
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import type { ZodType } from "zod/v4";
|
|
4
3
|
import type { SupipowersConfig } from "../types.js";
|
|
5
4
|
import { QualityGatesSchema } from "../quality/schemas.js";
|
|
6
5
|
import { UltraPlanConfigSchema } from "../ultraplan/contracts.js";
|
|
6
|
+
import { collectSchemaValidationErrors } from "../ai/schema-validation.js";
|
|
7
7
|
|
|
8
8
|
const TAG_FORMAT_PATTERN = "^(?:(?!\\$\\{version\\}).)*\\$\\{version\\}(?:(?!\\$\\{version\\}).)*$";
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
export const ConfigSchema =
|
|
11
|
+
export const ConfigSchema = z.object(
|
|
12
12
|
{
|
|
13
|
-
version:
|
|
14
|
-
quality:
|
|
13
|
+
version: z.string(),
|
|
14
|
+
quality: z.object(
|
|
15
15
|
{
|
|
16
16
|
gates: QualityGatesSchema,
|
|
17
17
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
lsp: Type.Object(
|
|
18
|
+
).strict(),
|
|
19
|
+
lsp: z.object(
|
|
21
20
|
{
|
|
22
|
-
setupGuide:
|
|
21
|
+
setupGuide: z.boolean(),
|
|
23
22
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
qa: Type.Object(
|
|
23
|
+
).strict(),
|
|
24
|
+
qa: z.object(
|
|
27
25
|
{
|
|
28
|
-
framework:
|
|
29
|
-
e2e:
|
|
26
|
+
framework: z.string().nullable(),
|
|
27
|
+
e2e: z.boolean(),
|
|
30
28
|
},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
release: Type.Object(
|
|
29
|
+
).strict(),
|
|
30
|
+
release: z.object(
|
|
34
31
|
{
|
|
35
|
-
channels:
|
|
36
|
-
tagFormat:
|
|
37
|
-
customChannels:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
),
|
|
46
|
-
),
|
|
32
|
+
channels: z.array(z.string()),
|
|
33
|
+
tagFormat: z.string().regex(new RegExp(TAG_FORMAT_PATTERN)),
|
|
34
|
+
customChannels: z.record(
|
|
35
|
+
z.string(),
|
|
36
|
+
z.object({
|
|
37
|
+
label: z.string(),
|
|
38
|
+
publishCommand: z.string(),
|
|
39
|
+
detectCommand: z.string().optional(),
|
|
40
|
+
}),
|
|
41
|
+
).optional(),
|
|
47
42
|
},
|
|
48
|
-
|
|
49
|
-
),
|
|
43
|
+
).strict(),
|
|
50
44
|
ultraplan: UltraPlanConfigSchema,
|
|
51
|
-
contextMode:
|
|
45
|
+
contextMode: z.object(
|
|
52
46
|
{
|
|
53
|
-
enabled:
|
|
54
|
-
compressionThreshold:
|
|
55
|
-
blockHttpCommands:
|
|
56
|
-
routingInstructions:
|
|
57
|
-
eventTracking:
|
|
58
|
-
compaction:
|
|
59
|
-
llmSummarization:
|
|
60
|
-
llmThreshold:
|
|
61
|
-
enforceRouting:
|
|
62
|
-
lazyTools:
|
|
47
|
+
enabled: z.boolean(),
|
|
48
|
+
compressionThreshold: z.number().min(1024),
|
|
49
|
+
blockHttpCommands: z.boolean(),
|
|
50
|
+
routingInstructions: z.boolean(),
|
|
51
|
+
eventTracking: z.boolean(),
|
|
52
|
+
compaction: z.boolean(),
|
|
53
|
+
llmSummarization: z.boolean(),
|
|
54
|
+
llmThreshold: z.number().min(4096),
|
|
55
|
+
enforceRouting: z.boolean(),
|
|
56
|
+
lazyTools: z.object(
|
|
63
57
|
{
|
|
64
|
-
enabled:
|
|
65
|
-
mode:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
]),
|
|
70
|
-
alwaysKeep: Type.Array(Type.String()),
|
|
71
|
-
commandAllowlist: Type.Record(Type.String(), Type.Array(Type.String())),
|
|
72
|
-
keywordTools: Type.Record(Type.String(), Type.Array(Type.String())),
|
|
58
|
+
enabled: z.boolean(),
|
|
59
|
+
mode: z.enum(["conservative", "balanced", "aggressive"]),
|
|
60
|
+
alwaysKeep: z.array(z.string()),
|
|
61
|
+
commandAllowlist: z.record(z.string(), z.array(z.string())),
|
|
62
|
+
keywordTools: z.record(z.string(), z.array(z.string())),
|
|
73
63
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
processors: Type.Object(
|
|
64
|
+
).strict(),
|
|
65
|
+
processors: z.object(
|
|
77
66
|
{
|
|
78
|
-
enabled:
|
|
79
|
-
disable:
|
|
80
|
-
|
|
81
|
-
Type.Literal("git"),
|
|
82
|
-
Type.Literal("test"),
|
|
83
|
-
Type.Literal("lint"),
|
|
84
|
-
Type.Literal("build"),
|
|
85
|
-
Type.Literal("k8s"),
|
|
86
|
-
Type.Literal("docker"),
|
|
87
|
-
Type.Literal("log"),
|
|
88
|
-
Type.Literal("json"),
|
|
89
|
-
]),
|
|
67
|
+
enabled: z.boolean(),
|
|
68
|
+
disable: z.array(
|
|
69
|
+
z.enum(["git", "test", "lint", "build", "k8s", "docker", "log", "json"]),
|
|
90
70
|
),
|
|
91
71
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
cacheHandles: Type.Object(
|
|
72
|
+
).strict(),
|
|
73
|
+
cacheHandles: z.object(
|
|
95
74
|
{
|
|
96
|
-
enabled:
|
|
97
|
-
spillThresholdBytes:
|
|
98
|
-
previewBytes:
|
|
75
|
+
enabled: z.boolean(),
|
|
76
|
+
spillThresholdBytes: z.number().min(1024),
|
|
77
|
+
previewBytes: z.number().min(256),
|
|
99
78
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
repomap: Type.Object(
|
|
79
|
+
).strict(),
|
|
80
|
+
repomap: z.object(
|
|
103
81
|
{
|
|
104
|
-
enabled:
|
|
105
|
-
tokenBudget:
|
|
106
|
-
maxFiles:
|
|
82
|
+
enabled: z.boolean(),
|
|
83
|
+
tokenBudget: z.number().min(100),
|
|
84
|
+
maxFiles: z.number().min(1),
|
|
107
85
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
memory: Type.Object(
|
|
86
|
+
).strict(),
|
|
87
|
+
memory: z.object(
|
|
111
88
|
{
|
|
112
|
-
enabled:
|
|
113
|
-
byteBudget:
|
|
114
|
-
maxRows:
|
|
115
|
-
retentionDays:
|
|
116
|
-
focusChainCadence:
|
|
89
|
+
enabled: z.boolean(),
|
|
90
|
+
byteBudget: z.number().min(256),
|
|
91
|
+
maxRows: z.number().min(1),
|
|
92
|
+
retentionDays: z.number().min(1),
|
|
93
|
+
focusChainCadence: z.number().int().min(1),
|
|
117
94
|
},
|
|
118
|
-
|
|
119
|
-
),
|
|
95
|
+
).strict(),
|
|
120
96
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
mempalace: Type.Object(
|
|
97
|
+
).strict(),
|
|
98
|
+
mempalace: z.object(
|
|
124
99
|
{
|
|
125
|
-
enabled:
|
|
126
|
-
packageVersion:
|
|
127
|
-
managedVenvPath:
|
|
128
|
-
palacePath:
|
|
129
|
-
defaultWingStrategy:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
explicitWing: Type.Union([Type.String(), Type.Null()]),
|
|
135
|
-
defaultAgentName: Type.String({ minLength: 1 }),
|
|
136
|
-
autoSetup: Type.Boolean(),
|
|
137
|
-
hooks: Type.Object(
|
|
100
|
+
enabled: z.boolean(),
|
|
101
|
+
packageVersion: z.string().min(1),
|
|
102
|
+
managedVenvPath: z.string().min(1),
|
|
103
|
+
palacePath: z.string().min(1),
|
|
104
|
+
defaultWingStrategy: z.enum(["repo-name", "project-slug", "explicit"]),
|
|
105
|
+
explicitWing: z.string().nullable(),
|
|
106
|
+
defaultAgentName: z.string().min(1),
|
|
107
|
+
autoSetup: z.boolean(),
|
|
108
|
+
hooks: z.object(
|
|
138
109
|
{
|
|
139
|
-
wakeUp:
|
|
140
|
-
searchGuidance:
|
|
141
|
-
autoSearchOnPrompt:
|
|
142
|
-
compactionCheckpoint:
|
|
143
|
-
shutdownDiary:
|
|
110
|
+
wakeUp: z.boolean(),
|
|
111
|
+
searchGuidance: z.boolean(),
|
|
112
|
+
autoSearchOnPrompt: z.boolean(),
|
|
113
|
+
compactionCheckpoint: z.boolean(),
|
|
114
|
+
shutdownDiary: z.boolean(),
|
|
144
115
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
budgets: Type.Object(
|
|
116
|
+
).strict(),
|
|
117
|
+
budgets: z.object(
|
|
148
118
|
{
|
|
149
|
-
wakeUpTokens:
|
|
150
|
-
searchResultChars:
|
|
151
|
-
listResultChars:
|
|
152
|
-
diaryChars:
|
|
153
|
-
autoSearchTokens:
|
|
154
|
-
wakeUpInjectionEvery:
|
|
155
|
-
autoSearchSimilarityFloor:
|
|
156
|
-
autoSearchBm25Floor:
|
|
119
|
+
wakeUpTokens: z.number().int().min(1),
|
|
120
|
+
searchResultChars: z.number().int().min(1),
|
|
121
|
+
listResultChars: z.number().int().min(1),
|
|
122
|
+
diaryChars: z.number().int().min(1),
|
|
123
|
+
autoSearchTokens: z.number().int().min(1),
|
|
124
|
+
wakeUpInjectionEvery: z.number().int().min(1),
|
|
125
|
+
autoSearchSimilarityFloor: z.number().min(0).max(1),
|
|
126
|
+
autoSearchBm25Floor: z.number().min(0),
|
|
157
127
|
},
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
timeouts: Type.Object(
|
|
128
|
+
).strict(),
|
|
129
|
+
timeouts: z.object(
|
|
161
130
|
{
|
|
162
|
-
setupMs:
|
|
163
|
-
bridgeMs:
|
|
164
|
-
hookMs:
|
|
131
|
+
setupMs: z.number().int().min(1),
|
|
132
|
+
bridgeMs: z.number().int().min(1),
|
|
133
|
+
hookMs: z.number().int().min(1),
|
|
165
134
|
},
|
|
166
|
-
|
|
167
|
-
),
|
|
135
|
+
).strict(),
|
|
168
136
|
},
|
|
169
|
-
|
|
170
|
-
),
|
|
137
|
+
).strict(),
|
|
171
138
|
},
|
|
172
|
-
|
|
173
|
-
);
|
|
139
|
+
).strict();
|
|
174
140
|
|
|
175
141
|
export interface ConfigParseError {
|
|
176
142
|
source: "global" | "root";
|
|
@@ -190,13 +156,10 @@ export interface InspectionLoadResult {
|
|
|
190
156
|
validationErrors: ConfigValidationError[];
|
|
191
157
|
}
|
|
192
158
|
|
|
193
|
-
function normalizeErrorPath(path: string): string {
|
|
194
|
-
return path.replace(/^\//, "").replace(/\//g, ".") || "(root)";
|
|
195
|
-
}
|
|
196
159
|
|
|
197
|
-
function collectValidationErrors(schema:
|
|
198
|
-
return
|
|
199
|
-
path:
|
|
160
|
+
function collectValidationErrors(schema: ZodType, data: unknown): ConfigValidationError[] {
|
|
161
|
+
return collectSchemaValidationErrors(schema, data).map((error) => ({
|
|
162
|
+
path: error.path,
|
|
200
163
|
message: error.message,
|
|
201
164
|
}));
|
|
202
165
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type { RuleMode, WriteRuleAction } from "./startup-optimizer.js";
|
|
1
|
+
import type { RuleMode, WriteCommandAction, WriteExtensionAction, WriteRuleAction } from "./startup-optimizer.js";
|
|
2
2
|
|
|
3
3
|
export const MANAGED_RULE_HEADER = "<!-- supipowers:managed-rule";
|
|
4
4
|
export const MANAGED_RULE_END = "-->";
|
|
5
|
+
export const MANAGED_COMMAND_HEADER = "<!-- supipowers:managed-command";
|
|
6
|
+
const MANAGED_COMMAND_FRONTMATTER_KEY = "supipowers-managed-command";
|
|
7
|
+
const MANAGED_COMMAND_FRONTMATTER_VERSION = "1";
|
|
8
|
+
export const MANAGED_EXTENSION_HEADER = "/* supipowers:managed-extension";
|
|
9
|
+
export const MANAGED_EXTENSION_END = "*/";
|
|
5
10
|
|
|
6
11
|
export interface ManagedRuleMetadata {
|
|
7
12
|
version: number;
|
|
@@ -13,6 +18,26 @@ export interface ManagedRuleMetadata {
|
|
|
13
18
|
sourceBytes: number;
|
|
14
19
|
}
|
|
15
20
|
|
|
21
|
+
export interface ManagedCommandMetadata {
|
|
22
|
+
version: number;
|
|
23
|
+
sourceId: string;
|
|
24
|
+
sourceName: string;
|
|
25
|
+
sourceHash: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
commandName: string;
|
|
28
|
+
sourceBytes: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ManagedExtensionMetadata {
|
|
32
|
+
version: number;
|
|
33
|
+
sourceId: string;
|
|
34
|
+
sourceName: string;
|
|
35
|
+
sourceHash: string;
|
|
36
|
+
slug: string;
|
|
37
|
+
extensionName: string;
|
|
38
|
+
sourceBytes: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
export type ParsedManagedRule =
|
|
17
42
|
| { status: "unmanaged"; managed: false }
|
|
18
43
|
| {
|
|
@@ -29,6 +54,27 @@ export type ParsedManagedRule =
|
|
|
29
54
|
}
|
|
30
55
|
| { status: "malformed"; managed: true; error: string };
|
|
31
56
|
|
|
57
|
+
export type ParsedManagedCommand =
|
|
58
|
+
| { status: "unmanaged"; managed: false }
|
|
59
|
+
| {
|
|
60
|
+
status: "managed";
|
|
61
|
+
managed: true;
|
|
62
|
+
metadata: ManagedCommandMetadata;
|
|
63
|
+
frontmatter: Record<string, string>;
|
|
64
|
+
body: string;
|
|
65
|
+
}
|
|
66
|
+
| { status: "malformed"; managed: true; error: string };
|
|
67
|
+
|
|
68
|
+
export type ParsedManagedExtension =
|
|
69
|
+
| { status: "unmanaged"; managed: false }
|
|
70
|
+
| {
|
|
71
|
+
status: "managed";
|
|
72
|
+
managed: true;
|
|
73
|
+
metadata: ManagedExtensionMetadata;
|
|
74
|
+
body: string;
|
|
75
|
+
}
|
|
76
|
+
| { status: "malformed"; managed: true; error: string };
|
|
77
|
+
|
|
32
78
|
/**
|
|
33
79
|
* Render a managed rule file.
|
|
34
80
|
*
|
|
@@ -56,6 +102,47 @@ export function renderManagedRule(action: WriteRuleAction): string {
|
|
|
56
102
|
return `${metadata}\n---\n${frontmatter}\n---\n${body}`;
|
|
57
103
|
}
|
|
58
104
|
|
|
105
|
+
export function renderManagedCommand(action: WriteCommandAction): string {
|
|
106
|
+
const description = action.description ?? `Run ${action.sourceName} on demand.`;
|
|
107
|
+
const body = action.sourceContent.endsWith("\n")
|
|
108
|
+
? action.sourceContent
|
|
109
|
+
: `${action.sourceContent}\n`;
|
|
110
|
+
|
|
111
|
+
return [
|
|
112
|
+
"---",
|
|
113
|
+
`description: ${frontmatterScalarLiteral(description)}`,
|
|
114
|
+
`${MANAGED_COMMAND_FRONTMATTER_KEY}: ${frontmatterScalarLiteral(MANAGED_COMMAND_FRONTMATTER_VERSION)}`,
|
|
115
|
+
`sourceId: ${frontmatterScalarLiteral(action.sourceId)}`,
|
|
116
|
+
`sourceName: ${frontmatterScalarLiteral(action.sourceName)}`,
|
|
117
|
+
`sourceHash: ${frontmatterScalarLiteral(action.sourceHash)}`,
|
|
118
|
+
`slug: ${frontmatterScalarLiteral(action.slug)}`,
|
|
119
|
+
`commandName: ${frontmatterScalarLiteral(action.commandName)}`,
|
|
120
|
+
`sourceBytes: ${action.sourceBytes}`,
|
|
121
|
+
"---",
|
|
122
|
+
body,
|
|
123
|
+
].join("\n");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function renderManagedExtension(action: WriteExtensionAction): string {
|
|
127
|
+
const metadata = [
|
|
128
|
+
MANAGED_EXTENSION_HEADER,
|
|
129
|
+
"version: 1",
|
|
130
|
+
`sourceId: ${action.sourceId}`,
|
|
131
|
+
`sourceName: ${action.sourceName}`,
|
|
132
|
+
`sourceHash: ${action.sourceHash}`,
|
|
133
|
+
`slug: ${action.slug}`,
|
|
134
|
+
`extensionName: ${action.extensionName}`,
|
|
135
|
+
`sourceBytes: ${action.sourceBytes}`,
|
|
136
|
+
MANAGED_EXTENSION_END,
|
|
137
|
+
].join("\n");
|
|
138
|
+
const body = action.sourceContent.endsWith("\n")
|
|
139
|
+
? action.sourceContent
|
|
140
|
+
: `${action.sourceContent}\n`;
|
|
141
|
+
|
|
142
|
+
return `${metadata}\n${body}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
59
146
|
export function parseManagedRule(text: string): ParsedManagedRule {
|
|
60
147
|
if (!text.startsWith(MANAGED_RULE_HEADER)) {
|
|
61
148
|
return { status: "unmanaged", managed: false };
|
|
@@ -87,12 +174,98 @@ export function parseManagedRule(text: string): ParsedManagedRule {
|
|
|
87
174
|
};
|
|
88
175
|
}
|
|
89
176
|
|
|
177
|
+
export function parseManagedCommand(text: string): ParsedManagedCommand {
|
|
178
|
+
if (text.startsWith(MANAGED_COMMAND_HEADER)) {
|
|
179
|
+
const headerEnd = text.indexOf(MANAGED_RULE_END, MANAGED_COMMAND_HEADER.length);
|
|
180
|
+
if (headerEnd === -1) {
|
|
181
|
+
return { status: "malformed", managed: true, error: "managed header is not closed" };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const headerText = text.slice(0, headerEnd).trimEnd();
|
|
185
|
+
const metadataResult = parseCommandMetadata(headerText);
|
|
186
|
+
if (typeof metadataResult === "string") {
|
|
187
|
+
return { status: "malformed", managed: true, error: metadataResult };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const afterHeader = text.slice(headerEnd + MANAGED_RULE_END.length).replace(/^\r?\n/, "");
|
|
191
|
+
const frontmatterResult = parseFrontmatter(afterHeader);
|
|
192
|
+
if (typeof frontmatterResult === "string") {
|
|
193
|
+
return { status: "malformed", managed: true, error: frontmatterResult };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
status: "managed",
|
|
198
|
+
managed: true,
|
|
199
|
+
metadata: metadataResult,
|
|
200
|
+
frontmatter: frontmatterResult.frontmatter,
|
|
201
|
+
body: stripTrailingNewline(frontmatterResult.body),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const frontmatterResult = parseFrontmatter(text);
|
|
206
|
+
if (typeof frontmatterResult === "string") {
|
|
207
|
+
if (
|
|
208
|
+
(text.startsWith("---\n") || text.startsWith("---\r\n")) &&
|
|
209
|
+
text.includes(`${MANAGED_COMMAND_FRONTMATTER_KEY}:`)
|
|
210
|
+
) {
|
|
211
|
+
return { status: "malformed", managed: true, error: frontmatterResult };
|
|
212
|
+
}
|
|
213
|
+
return { status: "unmanaged", managed: false };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!frontmatterResult.frontmatter[MANAGED_COMMAND_FRONTMATTER_KEY]) {
|
|
217
|
+
return { status: "unmanaged", managed: false };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const metadataResult = parseCommandFrontmatterMetadata(frontmatterResult.frontmatter);
|
|
221
|
+
if (typeof metadataResult === "string") {
|
|
222
|
+
return { status: "malformed", managed: true, error: metadataResult };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
status: "managed",
|
|
227
|
+
managed: true,
|
|
228
|
+
metadata: metadataResult,
|
|
229
|
+
frontmatter: frontmatterResult.frontmatter,
|
|
230
|
+
body: stripTrailingNewline(frontmatterResult.body),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function parseManagedExtension(text: string): ParsedManagedExtension {
|
|
235
|
+
if (!text.startsWith(MANAGED_EXTENSION_HEADER)) {
|
|
236
|
+
return { status: "unmanaged", managed: false };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const headerEnd = text.indexOf(MANAGED_EXTENSION_END, MANAGED_EXTENSION_HEADER.length);
|
|
240
|
+
if (headerEnd === -1) {
|
|
241
|
+
return { status: "malformed", managed: true, error: "managed extension header is not closed" };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const headerText = text.slice(0, headerEnd).trimEnd();
|
|
245
|
+
const metadataResult = parseExtensionMetadata(headerText);
|
|
246
|
+
if (typeof metadataResult === "string") {
|
|
247
|
+
return { status: "malformed", managed: true, error: metadataResult };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const body = text.slice(headerEnd + MANAGED_EXTENSION_END.length).replace(/^\r?\n/, "");
|
|
251
|
+
return {
|
|
252
|
+
status: "managed",
|
|
253
|
+
managed: true,
|
|
254
|
+
metadata: metadataResult,
|
|
255
|
+
body: stripTrailingNewline(body),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
90
259
|
function renderFrontmatter(action: WriteRuleAction): string {
|
|
91
260
|
if (action.mode === "ttsr") {
|
|
92
261
|
if (!action.condition) {
|
|
93
262
|
throw new Error(`TTSR rule ${action.slug} is missing a condition`);
|
|
94
263
|
}
|
|
95
|
-
return
|
|
264
|
+
return [
|
|
265
|
+
`condition: ${frontmatterScalarLiteral(action.condition)}`,
|
|
266
|
+
...(action.triggers ? [`triggers: ${frontmatterScalarLiteral(action.triggers)}`] : []),
|
|
267
|
+
`scope: ${frontmatterScalarLiteral(action.scope ?? "text")}`,
|
|
268
|
+
].join("\n");
|
|
96
269
|
}
|
|
97
270
|
|
|
98
271
|
const description = action.description ?? deriveRuleDescription(action.sourceContent) ?? `Use ${action.sourceName} when relevant.`;
|
|
@@ -165,6 +338,105 @@ function parseMetadata(headerText: string): ManagedRuleMetadata | string {
|
|
|
165
338
|
};
|
|
166
339
|
}
|
|
167
340
|
|
|
341
|
+
function parseCommandMetadata(headerText: string): ManagedCommandMetadata | string {
|
|
342
|
+
const lines = headerText.split(/\r?\n/);
|
|
343
|
+
if (lines[0] !== MANAGED_COMMAND_HEADER) {
|
|
344
|
+
return "managed command header marker is invalid";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const raw: Record<string, string> = {};
|
|
348
|
+
for (const line of lines.slice(1)) {
|
|
349
|
+
const idx = line.indexOf(":");
|
|
350
|
+
if (idx === -1) continue;
|
|
351
|
+
raw[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return parseCommandMetadataFields(raw);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function parseCommandFrontmatterMetadata(frontmatter: Record<string, string>): ManagedCommandMetadata | string {
|
|
358
|
+
if (frontmatter[MANAGED_COMMAND_FRONTMATTER_KEY] !== MANAGED_COMMAND_FRONTMATTER_VERSION) {
|
|
359
|
+
return "managed command metadata has invalid version";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return parseCommandMetadataFields({
|
|
363
|
+
version: frontmatter[MANAGED_COMMAND_FRONTMATTER_KEY],
|
|
364
|
+
sourceId: frontmatter.sourceId,
|
|
365
|
+
sourceName: frontmatter.sourceName,
|
|
366
|
+
sourceHash: frontmatter.sourceHash,
|
|
367
|
+
slug: frontmatter.slug,
|
|
368
|
+
commandName: frontmatter.commandName,
|
|
369
|
+
sourceBytes: frontmatter.sourceBytes,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function parseCommandMetadataFields(raw: Record<string, string | undefined>): ManagedCommandMetadata | string {
|
|
374
|
+
const versionRaw = raw.version;
|
|
375
|
+
const sourceId = raw.sourceId;
|
|
376
|
+
const sourceName = raw.sourceName;
|
|
377
|
+
const sourceHash = raw.sourceHash;
|
|
378
|
+
const slug = raw.slug;
|
|
379
|
+
const commandName = raw.commandName;
|
|
380
|
+
const sourceBytesRaw = raw.sourceBytes;
|
|
381
|
+
if (!versionRaw) return "managed command metadata missing version";
|
|
382
|
+
if (!sourceId) return "managed command metadata missing sourceId";
|
|
383
|
+
if (!sourceName) return "managed command metadata missing sourceName";
|
|
384
|
+
if (!sourceHash) return "managed command metadata missing sourceHash";
|
|
385
|
+
if (!slug) return "managed command metadata missing slug";
|
|
386
|
+
if (!commandName) return "managed command metadata missing commandName";
|
|
387
|
+
if (!sourceBytesRaw) return "managed command metadata missing sourceBytes";
|
|
388
|
+
|
|
389
|
+
const version = Number(versionRaw);
|
|
390
|
+
const sourceBytes = Number(sourceBytesRaw);
|
|
391
|
+
if (!Number.isInteger(version) || version <= 0) return "managed command metadata has invalid version";
|
|
392
|
+
if (!Number.isInteger(sourceBytes) || sourceBytes < 0) return "managed command metadata has invalid sourceBytes";
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
version,
|
|
396
|
+
sourceId,
|
|
397
|
+
sourceName,
|
|
398
|
+
sourceHash,
|
|
399
|
+
slug,
|
|
400
|
+
commandName,
|
|
401
|
+
sourceBytes,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function parseExtensionMetadata(headerText: string): ManagedExtensionMetadata | string {
|
|
406
|
+
const lines = headerText.split(/\r?\n/);
|
|
407
|
+
if (lines[0] !== MANAGED_EXTENSION_HEADER) {
|
|
408
|
+
return "managed extension header marker is invalid";
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const raw: Record<string, string> = {};
|
|
412
|
+
for (const line of lines.slice(1)) {
|
|
413
|
+
const idx = line.indexOf(":");
|
|
414
|
+
if (idx === -1) continue;
|
|
415
|
+
raw[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const required = ["version", "sourceId", "sourceName", "sourceHash", "slug", "extensionName", "sourceBytes"];
|
|
419
|
+
for (const key of required) {
|
|
420
|
+
if (!raw[key]) return `managed extension metadata missing ${key}`;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const version = Number(raw.version);
|
|
424
|
+
const sourceBytes = Number(raw.sourceBytes);
|
|
425
|
+
if (!Number.isInteger(version) || version <= 0) return "managed extension metadata has invalid version";
|
|
426
|
+
if (!Number.isInteger(sourceBytes) || sourceBytes < 0) return "managed extension metadata has invalid sourceBytes";
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
version,
|
|
430
|
+
sourceId: raw.sourceId,
|
|
431
|
+
sourceName: raw.sourceName,
|
|
432
|
+
sourceHash: raw.sourceHash,
|
|
433
|
+
slug: raw.slug,
|
|
434
|
+
extensionName: raw.extensionName,
|
|
435
|
+
sourceBytes,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
|
|
168
440
|
function parseFrontmatter(text: string): { frontmatter: Record<string, string>; body: string } | string {
|
|
169
441
|
if (!text.startsWith("---\n") && !text.startsWith("---\r\n")) {
|
|
170
442
|
return "managed frontmatter is missing opening delimiter";
|