@nseng-ai/plans 0.1.1 → 0.1.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/package.json +4 -4
- package/src/content-slug-derivation.ts +48 -90
- package/src/index.ts +1 -1
- package/src/saved-plan-content-slug.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nseng-ai/plans",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src"
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"./testing": "./src/testing.ts"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@nseng-ai/capability-kit": "0.1.
|
|
25
|
-
"@nseng-ai/clinkr": "0.1.
|
|
26
|
-
"@nseng-ai/foundation": "0.1.
|
|
24
|
+
"@nseng-ai/capability-kit": "0.1.2",
|
|
25
|
+
"@nseng-ai/clinkr": "0.1.2",
|
|
26
|
+
"@nseng-ai/foundation": "0.1.2",
|
|
27
27
|
"zod": "^4.4.3"
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
buildKitContentSlugPrompt,
|
|
3
|
+
deriveKitContentSlug,
|
|
4
|
+
normalizeContentSlugOutput,
|
|
5
|
+
truncateContentForSlug,
|
|
6
|
+
type ContentSlugEvidence,
|
|
7
|
+
type KitContentSlugDerivationVariant,
|
|
8
|
+
} from "@nseng-ai/capability-kit/content-slug";
|
|
9
|
+
import type { CommandExecApi } from "@nseng-ai/foundation/exec";
|
|
3
10
|
import { MAX_PLAN_SLUG_WORDS, MIN_PLAN_SLUG_WORDS, validatePlanSlug } from "./plan-persistence.ts";
|
|
4
11
|
|
|
5
|
-
const MAX_ERROR_CHARS = 4_000;
|
|
6
|
-
|
|
7
12
|
export const MAX_PLAN_CONTENT_CHARS = 32_000;
|
|
8
13
|
|
|
9
|
-
export interface
|
|
14
|
+
export interface PlanContentSlugVariantSeed {
|
|
10
15
|
slugKind: string;
|
|
11
16
|
promptIntroLines: readonly string[];
|
|
12
17
|
invalidSlugMessage: string;
|
|
@@ -20,108 +25,61 @@ export interface DeriveContentSlugInput {
|
|
|
20
25
|
signal?: AbortSignal;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
export type ContentSlugEvidence
|
|
28
|
+
export type { ContentSlugEvidence };
|
|
24
29
|
|
|
25
30
|
export async function deriveContentSlug(
|
|
26
31
|
pi: CommandExecApi,
|
|
27
32
|
input: DeriveContentSlugInput,
|
|
28
|
-
variant:
|
|
33
|
+
variant: PlanContentSlugVariantSeed,
|
|
29
34
|
): Promise<ContentSlugEvidence> {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
slugKind: variant.slugKind,
|
|
36
|
-
normalizeOutput: normalizePlanContentSlugOutput,
|
|
37
|
-
exec: (command, args, options) => pi.exec(command, args, options),
|
|
38
|
-
});
|
|
39
|
-
if (!result.ok) {
|
|
40
|
-
throw slugDerivationFailed(variant, result.failure.lines);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const { slug, rawOutput } = result.evidence;
|
|
44
|
-
const slugError = validatePlanSlug(slug);
|
|
45
|
-
if (slugError !== undefined) {
|
|
46
|
-
throw slugDerivationFailed(variant, [
|
|
47
|
-
variant.invalidSlugMessage,
|
|
48
|
-
`Normalized slug: ${slug}`,
|
|
49
|
-
`Reason: ${slugError}`,
|
|
50
|
-
formatOutputSection("stdout", rawOutput, { maxChars: MAX_ERROR_CHARS, maxLines: 80 }),
|
|
51
|
-
]);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return result.evidence;
|
|
35
|
+
return deriveKitContentSlug(
|
|
36
|
+
{ exec: (command, args, options) => pi.exec(command, args, options) },
|
|
37
|
+
input,
|
|
38
|
+
toKitContentSlugVariant(variant),
|
|
39
|
+
);
|
|
55
40
|
}
|
|
56
41
|
|
|
57
42
|
export function buildContentSlugPrompt(
|
|
58
43
|
content: string,
|
|
59
|
-
variant:
|
|
44
|
+
variant: PlanContentSlugVariantSeed,
|
|
60
45
|
): string {
|
|
61
|
-
return
|
|
62
|
-
...variant.promptIntroLines,
|
|
63
|
-
"Return exactly one slug and no prose.",
|
|
64
|
-
"Rules:",
|
|
65
|
-
"- Use lowercase ASCII kebab-case words separated by single hyphens.",
|
|
66
|
-
`- Use ${MIN_PLAN_SLUG_WORDS}–${MAX_PLAN_SLUG_WORDS} words.`,
|
|
67
|
-
"- Make the slug specific to the implementation described by the plan.",
|
|
68
|
-
"- Prefer concrete deliverables and nouns from the plan body.",
|
|
69
|
-
"- Do not use dates, random IDs, generic-only slugs, or the saved-plan filename.",
|
|
70
|
-
"",
|
|
71
|
-
"## Plan content",
|
|
72
|
-
truncatePlanContentForSlug(displayPlanContentForSlug(content)),
|
|
73
|
-
].join("\n");
|
|
46
|
+
return buildKitContentSlugPrompt(content, toKitContentSlugVariant(variant));
|
|
74
47
|
}
|
|
75
48
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
49
|
+
const PLAN_CONTENT_SLUG_NORMALIZATION = {
|
|
50
|
+
maxWords: MAX_PLAN_SLUG_WORDS,
|
|
51
|
+
stripSuffixes: ["-plan"],
|
|
52
|
+
} satisfies KitContentSlugDerivationVariant["normalization"];
|
|
80
53
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
54
|
+
const PLAN_CONTENT_SLUG_TRUNCATION = {
|
|
55
|
+
maxContentChars: MAX_PLAN_CONTENT_CHARS,
|
|
56
|
+
truncationMessage: "[Plan content truncated for slug generation]",
|
|
57
|
+
} satisfies Pick<KitContentSlugDerivationVariant, "maxContentChars" | "truncationMessage">;
|
|
86
58
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.normalize("NFKD")
|
|
90
|
-
.replace(/[\u0300-\u036f]/g, "")
|
|
91
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
92
|
-
.replace(/-+/g, "-")
|
|
93
|
-
.replace(/^-|-$/g, "");
|
|
94
|
-
const withoutPlanSuffix = slug.replace(/(?:-plan)+$/g, "").replace(/^-|-$/g, "");
|
|
95
|
-
if (withoutPlanSuffix.length === 0) {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const repaired = withoutPlanSuffix
|
|
100
|
-
.split("-")
|
|
101
|
-
.filter(Boolean)
|
|
102
|
-
.slice(0, MAX_PLAN_SLUG_WORDS)
|
|
103
|
-
.join("-");
|
|
104
|
-
return repaired.length > 0 ? repaired : undefined;
|
|
59
|
+
export function normalizePlanContentSlugOutput(value: string): string | undefined {
|
|
60
|
+
return normalizeContentSlugOutput(value, PLAN_CONTENT_SLUG_NORMALIZATION);
|
|
105
61
|
}
|
|
106
62
|
|
|
107
63
|
export function truncatePlanContentForSlug(content: string): string {
|
|
108
|
-
|
|
109
|
-
return content;
|
|
110
|
-
}
|
|
111
|
-
return `${content.slice(0, MAX_PLAN_CONTENT_CHARS)}\n\n[Plan content truncated for slug generation]`;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function firstNonEmptyModelOutputLine(value: string): string | undefined {
|
|
115
|
-
return value
|
|
116
|
-
.replace(/```[\s\S]*?```/g, (match) => match.replace(/```[a-zA-Z]*\n?|```/g, ""))
|
|
117
|
-
.split("\n")
|
|
118
|
-
.map((line) => line.trim())
|
|
119
|
-
.find((line) => line.length > 0);
|
|
64
|
+
return truncateContentForSlug(content, PLAN_CONTENT_SLUG_TRUNCATION);
|
|
120
65
|
}
|
|
121
66
|
|
|
122
|
-
function
|
|
123
|
-
variant:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
67
|
+
function toKitContentSlugVariant(
|
|
68
|
+
variant: PlanContentSlugVariantSeed,
|
|
69
|
+
): KitContentSlugDerivationVariant {
|
|
70
|
+
return {
|
|
71
|
+
...variant,
|
|
72
|
+
promptRuleLines: [
|
|
73
|
+
"- Use lowercase ASCII kebab-case words separated by single hyphens.",
|
|
74
|
+
`- Use ${MIN_PLAN_SLUG_WORDS}–${MAX_PLAN_SLUG_WORDS} words.`,
|
|
75
|
+
"- Make the slug specific to the implementation described by the plan.",
|
|
76
|
+
"- Prefer concrete deliverables and nouns from the plan body.",
|
|
77
|
+
"- Do not use dates, random IDs, generic-only slugs, or the saved-plan filename.",
|
|
78
|
+
],
|
|
79
|
+
contentHeading: "## Plan content",
|
|
80
|
+
emptyContentPlaceholder: "(empty plan content)",
|
|
81
|
+
...PLAN_CONTENT_SLUG_TRUNCATION,
|
|
82
|
+
normalization: PLAN_CONTENT_SLUG_NORMALIZATION,
|
|
83
|
+
validateSlug: validatePlanSlug,
|
|
84
|
+
};
|
|
127
85
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ export {
|
|
|
5
5
|
MAX_PLAN_CONTENT_CHARS,
|
|
6
6
|
normalizePlanContentSlugOutput,
|
|
7
7
|
truncatePlanContentForSlug,
|
|
8
|
-
type
|
|
8
|
+
type PlanContentSlugVariantSeed,
|
|
9
9
|
type ContentSlugEvidence,
|
|
10
10
|
type DeriveContentSlugInput,
|
|
11
11
|
} from "./content-slug-derivation.ts";
|
|
@@ -2,13 +2,13 @@ import type { CommandExecApi } from "@nseng-ai/foundation/exec";
|
|
|
2
2
|
import {
|
|
3
3
|
buildContentSlugPrompt,
|
|
4
4
|
deriveContentSlug,
|
|
5
|
-
type
|
|
5
|
+
type PlanContentSlugVariantSeed,
|
|
6
6
|
type ContentSlugEvidence,
|
|
7
7
|
} from "./content-slug-derivation.ts";
|
|
8
8
|
|
|
9
9
|
export type SavedPlanContentSlugEvidence = ContentSlugEvidence;
|
|
10
10
|
|
|
11
|
-
const SAVED_PLAN_CONTENT_SLUG_VARIANT:
|
|
11
|
+
const SAVED_PLAN_CONTENT_SLUG_VARIANT: PlanContentSlugVariantSeed = {
|
|
12
12
|
slugKind: "saved-plan filename slug",
|
|
13
13
|
promptIntroLines: [
|
|
14
14
|
"Generate the saved-plan filename slug for the Markdown implementation plan content below.",
|