@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nseng-ai/plans",
3
- "version": "0.1.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.1",
25
- "@nseng-ai/clinkr": "0.1.1",
26
- "@nseng-ai/foundation": "0.1.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 { type CommandExecApi, formatOutputSection } from "@nseng-ai/foundation/exec";
2
- import { deriveSlugWithModel, type SlugModelEvidence } from "@nseng-ai/capability-kit/model-slug";
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 ContentSlugDerivationVariant {
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 = SlugModelEvidence;
28
+ export type { ContentSlugEvidence };
24
29
 
25
30
  export async function deriveContentSlug(
26
31
  pi: CommandExecApi,
27
32
  input: DeriveContentSlugInput,
28
- variant: ContentSlugDerivationVariant,
33
+ variant: PlanContentSlugVariantSeed,
29
34
  ): Promise<ContentSlugEvidence> {
30
- const prompt = buildContentSlugPrompt(input.content, variant);
31
- const result = await deriveSlugWithModel({
32
- cwd: input.cwd,
33
- prompt,
34
- ...(input.signal === undefined ? {} : { signal: input.signal }),
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: ContentSlugDerivationVariant,
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
- function displayPlanContentForSlug(content: string): string {
77
- const trimmed = content.trim();
78
- return trimmed.length > 0 ? trimmed : "(empty plan content)";
79
- }
49
+ const PLAN_CONTENT_SLUG_NORMALIZATION = {
50
+ maxWords: MAX_PLAN_SLUG_WORDS,
51
+ stripSuffixes: ["-plan"],
52
+ } satisfies KitContentSlugDerivationVariant["normalization"];
80
53
 
81
- export function normalizePlanContentSlugOutput(value: string): string | undefined {
82
- const firstLine = firstNonEmptyModelOutputLine(value);
83
- if (firstLine === undefined) {
84
- return undefined;
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
- const slug = firstLine
88
- .toLowerCase()
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
- if (content.length <= MAX_PLAN_CONTENT_CHARS) {
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 slugDerivationFailed(
123
- variant: ContentSlugDerivationVariant,
124
- lines: readonly string[],
125
- ): Error {
126
- return new Error([variant.failureHeader, ...lines, variant.noFallbackLine].join("\n"));
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 ContentSlugDerivationVariant,
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 ContentSlugDerivationVariant,
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: ContentSlugDerivationVariant = {
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.",