@agent-plan/core 0.2.25 → 0.2.26

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.
Files changed (48) hide show
  1. package/dist/display-status.d.ts +3 -3
  2. package/dist/display-status.d.ts.map +1 -1
  3. package/dist/display-status.js +5 -4
  4. package/dist/handoff-context.d.ts +91 -1
  5. package/dist/handoff-context.d.ts.map +1 -1
  6. package/dist/handoff-context.js +134 -2
  7. package/dist/index.d.ts +5 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -0
  10. package/dist/naming.d.ts +3 -0
  11. package/dist/naming.d.ts.map +1 -1
  12. package/dist/naming.js +7 -0
  13. package/dist/payload-fallback.d.ts +38 -0
  14. package/dist/payload-fallback.d.ts.map +1 -0
  15. package/dist/payload-fallback.js +73 -0
  16. package/dist/plan-store.d.ts +65 -8
  17. package/dist/plan-store.d.ts.map +1 -1
  18. package/dist/plan-store.js +391 -39
  19. package/dist/planner-rules.d.ts.map +1 -1
  20. package/dist/planner-rules.js +4 -2
  21. package/dist/planner-skill.d.ts +24 -0
  22. package/dist/planner-skill.d.ts.map +1 -0
  23. package/dist/planner-skill.js +113 -0
  24. package/dist/project-context-migration.d.ts +47 -0
  25. package/dist/project-context-migration.d.ts.map +1 -0
  26. package/dist/project-context-migration.js +168 -0
  27. package/dist/read-tracking.d.ts +20 -10
  28. package/dist/read-tracking.d.ts.map +1 -1
  29. package/dist/read-tracking.js +35 -29
  30. package/dist/recap.d.ts.map +1 -1
  31. package/dist/recap.js +1 -0
  32. package/dist/refs.d.ts +6 -1
  33. package/dist/refs.d.ts.map +1 -1
  34. package/dist/refs.js +25 -0
  35. package/dist/renderer.d.ts.map +1 -1
  36. package/dist/renderer.js +24 -1
  37. package/dist/requirement-macro-tasks.d.ts +18 -0
  38. package/dist/requirement-macro-tasks.d.ts.map +1 -0
  39. package/dist/requirement-macro-tasks.js +55 -0
  40. package/dist/schema.d.ts +1088 -273
  41. package/dist/schema.d.ts.map +1 -1
  42. package/dist/schema.js +65 -0
  43. package/dist/task-start-outcome.d.ts +1 -1
  44. package/dist/task-start-outcome.d.ts.map +1 -1
  45. package/dist/task-start-outcome.js +1 -0
  46. package/package.json +3 -1
  47. package/planner-skill.md +210 -0
  48. package/skills/grill-me/SKILL.md +10 -0
@@ -1 +1 @@
1
- {"version":3,"file":"planner-rules.d.ts","sourceRoot":"","sources":["../src/planner-rules.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,uBAAuB,EAAE,MAAM,EA2B3C,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAUD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW/E"}
1
+ {"version":3,"file":"planner-rules.d.ts","sourceRoot":"","sources":["../src/planner-rules.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,uBAAuB,EAAE,MAAM,EA6B3C,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAUD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW/E"}
@@ -10,8 +10,8 @@ import { join } from "node:path";
10
10
  * AGENTS.md governs ONLY the development of the agent-plan extension and must
11
11
  * not duplicate these rules.
12
12
  */
13
- const DETAIL_WRITING_RULE = "Write relevant points (decisions, constraints, current state, file:line refs, edge cases) into the task/phase/feature description or notes as soon as they emerge. For starting, resuming, or switching, call the lifecycle tool first so valid session attestations can be reused. If it denies the operation, perform only the missing or stale full reads listed in nextActions, in that exact order, then retry; read linked requirements explicitly only when requested. Cite entities with composite IDs, not bare UUIDs.";
14
- const EXPECTED_OPERATIONAL_RULE = "When you begin work, task_start and task_switch enforce session-scoped context reads and return precise missing/stale read actions when needed. Read any relevant phase handoff as additional context, then update the planner before and after significant changes. If you change an architectural decision, document it explicitly.";
13
+ const DETAIL_WRITING_RULE = "Write relevant points (decisions, constraints, current state, file:line refs, edge cases) into the task/phase/feature description or notes as soon as they emerge. For starting, resuming, or switching, call the lifecycle tool first so valid session attestations can be reused. If it denies the operation, perform only the missing or stale full reads listed in nextActions, in any order within the current session, then retry; read linked requirements explicitly only when requested. Cite entities with composite IDs, not bare UUIDs.";
14
+ const EXPECTED_OPERATIONAL_RULE = "When you begin work, task_start and task_switch enforce session-scoped context reads plus a Project Guidelines read when that project section exists and is missing or stale for the current session. Read any relevant phase handoff as additional context, then update the planner before and after significant changes. If you change an architectural decision, document it explicitly.";
15
15
  const LEGACY_DETAIL_WRITING_RULE = "Write relevant points (decisions, constraints, current state, file:line refs, edge cases) into the task/phase/feature description or notes as soon as they emerge. Before starting, resuming, or switching to a task, read task_get(full=true), then its parent phase_get(full=true), then its parent feature_get(full=true), in that exact order; read linked requirements explicitly when present. Cite entities with composite IDs, not bare UUIDs.";
16
16
  const LEGACY_EXPECTED_OPERATIONAL_RULE = "When you begin work, task_start and task_switch enforce the required ordered full reads. Read any relevant phase handoff as additional context, then update the planner before and after significant changes. If you change an architectural decision, document it explicitly.";
17
17
  export const PLANNER_EXTENSION_RULES = [
@@ -21,6 +21,8 @@ export const PLANNER_EXTENSION_RULES = [
21
21
  "Respect the task lifecycle strictly. Always call task_start before touching code, and task_complete with durable evidence of shipped work, verification (including partial verification), remaining/unverified work, files, and decisions when a deliverable is done. Never enter in-progress or done through task_update. Sync state changes (start/complete/block) to the planner at the exact moment they happen — never batch updates at session end. If task_start is denied, the task remains planned: satisfy the stated read prerequisites and retry, and never claim work started without an explicit successful start result. A task marked in-progress means you are actually working on it; if you stop, close or block it with a motivation in statusLog. Derived feature/phase status is computed from tasks, not stored in JSON.",
22
22
  // §3 — markdown not source of truth
23
23
  "Do not treat markdown as the source of truth for the plan. The plan's primary source is structured data in .planner/; markdown is a generated, human/agent-readable view.",
24
+ // §4 — project guidelines
25
+ "When a project defines the canonical Project Guidelines section, read it on planner load and before starting or switching task work whenever the current-session attestation is missing or stale. Keep coding standards, formatting, styling, and other project-specific rules from that section in working memory while executing the task.",
24
26
  // §5 — plan location
25
27
  "The plan lives in .planner/ within the target project. Whether .planner/ is git-tracked is at the project's discretion.",
26
28
  // §6 — discuss per phase
@@ -0,0 +1,24 @@
1
+ export type PlannerSkillSyncStatus = "created" | "current" | "updated" | "customized";
2
+ export interface PlannerSkillSyncResult {
3
+ path: string;
4
+ status: PlannerSkillSyncStatus;
5
+ content: string;
6
+ canonicalHash: string;
7
+ customized: boolean;
8
+ message: string;
9
+ }
10
+ export declare function plannerSkillHash(content: string): string;
11
+ export declare function renderManagedPlannerSkill(content: string): string;
12
+ export declare function managedPlannerSkillBody(content: string): string;
13
+ export declare function loadCanonicalPlannerSkill(): Promise<string>;
14
+ export declare function loadCanonicalGrillMeSkill(): Promise<string>;
15
+ /** Create or safely refresh `.planner/SKILL.md`. */
16
+ export declare function syncProjectPlannerSkill(plannerRoot: string): Promise<PlannerSkillSyncResult>;
17
+ /** Create or safely refresh `.planner/skills/grill-me/SKILL.md`. */
18
+ export declare function syncProjectGrillMeSkill(plannerRoot: string): Promise<PlannerSkillSyncResult>;
19
+ /**
20
+ * Load the project-local Ideas discussion skill, safely creating/upgrading the
21
+ * managed copy first. Customized project copies are returned unchanged.
22
+ */
23
+ export declare function loadProjectGrillMeSkill(plannerRoot: string): Promise<string>;
24
+ //# sourceMappingURL=planner-skill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner-skill.d.ts","sourceRoot":"","sources":["../src/planner-skill.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAEtF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,sBAAsB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAcD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEjE;AAED,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,MAAM,CAAC,CAEjE;AAyED,oDAAoD;AACpD,wBAAsB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAYlG;AAED,oEAAoE;AACpE,wBAAsB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAYlG;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAElF"}
@@ -0,0 +1,113 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ const MANAGED_SKILL_HEADER = /^<!-- agent-plan-managed-skill sha256:([a-f0-9]{64}) -->\n/;
5
+ const CANONICAL_SKILL_URL = new URL("../planner-skill.md", import.meta.url);
6
+ const CANONICAL_GRILL_ME_SKILL_URL = new URL("../skills/grill-me/SKILL.md", import.meta.url);
7
+ function normalizeSkillContent(content) {
8
+ return `${content.replace(/\r\n/g, "\n").trimEnd()}\n`;
9
+ }
10
+ export function plannerSkillHash(content) {
11
+ return createHash("sha256").update(normalizeSkillContent(content), "utf8").digest("hex");
12
+ }
13
+ export function renderManagedPlannerSkill(content) {
14
+ const normalized = normalizeSkillContent(content);
15
+ return `<!-- agent-plan-managed-skill sha256:${plannerSkillHash(normalized)} -->\n${normalized}`;
16
+ }
17
+ export function managedPlannerSkillBody(content) {
18
+ return normalizeSkillContent(content.replace(MANAGED_SKILL_HEADER, ""));
19
+ }
20
+ export async function loadCanonicalPlannerSkill() {
21
+ return normalizeSkillContent(await readFile(CANONICAL_SKILL_URL, "utf8"));
22
+ }
23
+ export async function loadCanonicalGrillMeSkill() {
24
+ return normalizeSkillContent(await readFile(CANONICAL_GRILL_ME_SKILL_URL, "utf8"));
25
+ }
26
+ async function syncManagedProjectSkill(path, canonical, messages) {
27
+ const canonicalHash = plannerSkillHash(canonical);
28
+ const managedCanonical = renderManagedPlannerSkill(canonical);
29
+ const existing = await readFile(path, "utf8").catch(() => null);
30
+ if (existing == null) {
31
+ await mkdir(dirname(path), { recursive: true });
32
+ await writeFile(path, managedCanonical, "utf8");
33
+ return {
34
+ path,
35
+ status: "created",
36
+ content: canonical,
37
+ canonicalHash,
38
+ customized: false,
39
+ message: messages.created,
40
+ };
41
+ }
42
+ if (normalizeSkillContent(existing) === normalizeSkillContent(managedCanonical)) {
43
+ return {
44
+ path,
45
+ status: "current",
46
+ content: canonical,
47
+ canonicalHash,
48
+ customized: false,
49
+ message: messages.current,
50
+ };
51
+ }
52
+ // Adopt an exact unmarked canonical copy. This supports projects that copied
53
+ // a canonical skill before managed headers were introduced.
54
+ if (!MANAGED_SKILL_HEADER.test(existing) && normalizeSkillContent(existing) === canonical) {
55
+ await writeFile(path, managedCanonical, "utf8");
56
+ return {
57
+ path,
58
+ status: "updated",
59
+ content: canonical,
60
+ canonicalHash,
61
+ customized: false,
62
+ message: messages.adopted,
63
+ };
64
+ }
65
+ const marker = existing.match(MANAGED_SKILL_HEADER);
66
+ const existingBody = managedPlannerSkillBody(existing);
67
+ if (marker && plannerSkillHash(existingBody) === marker[1]) {
68
+ await writeFile(path, managedCanonical, "utf8");
69
+ return {
70
+ path,
71
+ status: "updated",
72
+ content: canonical,
73
+ canonicalHash,
74
+ customized: false,
75
+ message: messages.updated,
76
+ };
77
+ }
78
+ return {
79
+ path,
80
+ status: "customized",
81
+ content: existingBody,
82
+ canonicalHash,
83
+ customized: true,
84
+ message: messages.customized,
85
+ };
86
+ }
87
+ /** Create or safely refresh `.planner/SKILL.md`. */
88
+ export async function syncProjectPlannerSkill(plannerRoot) {
89
+ return syncManagedProjectSkill(join(plannerRoot, "SKILL.md"), await loadCanonicalPlannerSkill(), {
90
+ created: "Created the canonical project-local planner skill.",
91
+ current: "The project-local planner skill matches the canonical version.",
92
+ adopted: "Adopted and marked the unmodified canonical planner skill.",
93
+ updated: "Updated the unmodified project-local planner skill to the current canonical version.",
94
+ customized: "Preserved the customized .planner/SKILL.md. Reconcile it manually with the current canonical Agent Plan guide; Agent Plan did not overwrite project instructions.",
95
+ });
96
+ }
97
+ /** Create or safely refresh `.planner/skills/grill-me/SKILL.md`. */
98
+ export async function syncProjectGrillMeSkill(plannerRoot) {
99
+ return syncManagedProjectSkill(join(plannerRoot, "skills", "grill-me", "SKILL.md"), await loadCanonicalGrillMeSkill(), {
100
+ created: "Created the canonical project-local grill-me skill for Ideas discussions.",
101
+ current: "The project-local grill-me skill matches the canonical version.",
102
+ adopted: "Adopted and marked the unmodified canonical grill-me skill.",
103
+ updated: "Updated the unmodified project-local grill-me skill to the current canonical version.",
104
+ customized: "Preserved the customized .planner/skills/grill-me/SKILL.md. Reconcile it manually with the current canonical Agent Plan skill; Agent Plan did not overwrite project instructions.",
105
+ });
106
+ }
107
+ /**
108
+ * Load the project-local Ideas discussion skill, safely creating/upgrading the
109
+ * managed copy first. Customized project copies are returned unchanged.
110
+ */
111
+ export async function loadProjectGrillMeSkill(plannerRoot) {
112
+ return (await syncProjectGrillMeSkill(plannerRoot)).content;
113
+ }
@@ -0,0 +1,47 @@
1
+ import type { AcceptedDecision, Project } from "./schema.js";
2
+ export declare const LEGACY_PROJECT_CONTEXT_MIGRATION_VERSION = 1;
3
+ export type LegacyGuidelineSource = "global-rules" | "before-phase-start" | "before-task-start" | "after-phase-complete";
4
+ export interface LegacyGuidelineAddition {
5
+ source: LegacyGuidelineSource;
6
+ text: string;
7
+ }
8
+ export type LegacyDecisionAddition = Omit<AcceptedDecision, "acceptedAt">;
9
+ export interface LegacyProjectContextMigrationPreview {
10
+ version: number;
11
+ hasLegacyContext: boolean;
12
+ guidelinesChanged: boolean;
13
+ acceptedDecisionsChanged: boolean;
14
+ guidelineAdditions: LegacyGuidelineAddition[];
15
+ acceptedDecisionAdditions: LegacyDecisionAddition[];
16
+ skippedGuidelineDuplicates: number;
17
+ skippedDecisionDuplicates: number;
18
+ resultingGuidelinesContent: string;
19
+ legacyCounts: {
20
+ globalRules: number;
21
+ workflowRules: number;
22
+ decisions: number;
23
+ };
24
+ fieldsClearedOnApply: Array<"globalRules" | "workflowRules" | "decisions">;
25
+ }
26
+ export interface LegacyProjectContextMigrationResult {
27
+ applied: boolean;
28
+ preview: LegacyProjectContextMigrationPreview;
29
+ project: Project;
30
+ }
31
+ export interface PlannerSessionPreparationResult {
32
+ changed: boolean;
33
+ project: Project;
34
+ legacyProjectContext: {
35
+ migrated: boolean;
36
+ version: number;
37
+ guidelinesAdded: number;
38
+ acceptedDecisionsAdded: number;
39
+ duplicatesSkipped: number;
40
+ clearedFields: Array<"globalRules" | "workflowRules" | "decisions">;
41
+ summary: string;
42
+ };
43
+ }
44
+ export declare function plannerSessionPreparationResult(result: LegacyProjectContextMigrationResult): PlannerSessionPreparationResult;
45
+ export declare function previewLegacyProjectContextMigration(project: Project): LegacyProjectContextMigrationPreview;
46
+ export declare function applyLegacyProjectContextMigration(project: Project, acceptedAt: string): LegacyProjectContextMigrationResult;
47
+ //# sourceMappingURL=project-context-migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-context-migration.d.ts","sourceRoot":"","sources":["../src/project-context-migration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAC;AAE5E,eAAO,MAAM,wCAAwC,IAAI,CAAC;AAE1D,MAAM,MAAM,qBAAqB,GAC7B,cAAc,GACd,oBAAoB,GACpB,mBAAmB,GACnB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC;AAE1E,MAAM,WAAW,oCAAoC;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,wBAAwB,EAAE,OAAO,CAAC;IAClC,kBAAkB,EAAE,uBAAuB,EAAE,CAAC;IAC9C,yBAAyB,EAAE,sBAAsB,EAAE,CAAC;IACpD,0BAA0B,EAAE,MAAM,CAAC;IACnC,yBAAyB,EAAE,MAAM,CAAC;IAClC,0BAA0B,EAAE,MAAM,CAAC;IACnC,YAAY,EAAE;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,oBAAoB,EAAE,KAAK,CAAC,aAAa,GAAG,eAAe,GAAG,WAAW,CAAC,CAAC;CAC5E;AAED,MAAM,WAAW,mCAAmC;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,oCAAoC,CAAC;IAC9C,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,oBAAoB,EAAE;QACpB,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,eAAe,EAAE,MAAM,CAAC;QACxB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,EAAE,KAAK,CAAC,aAAa,GAAG,eAAe,GAAG,WAAW,CAAC,CAAC;QACpE,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,mCAAmC,GAC1C,+BAA+B,CAoBjC;AA0DD,wBAAgB,oCAAoC,CAAC,OAAO,EAAE,OAAO,GAAG,oCAAoC,CAiE3G;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,mCAAmC,CA0BrC"}
@@ -0,0 +1,168 @@
1
+ import { createHash } from "node:crypto";
2
+ export const LEGACY_PROJECT_CONTEXT_MIGRATION_VERSION = 1;
3
+ export function plannerSessionPreparationResult(result) {
4
+ const guidelinesAdded = result.preview.guidelineAdditions.length;
5
+ const acceptedDecisionsAdded = result.preview.acceptedDecisionAdditions.length;
6
+ const duplicatesSkipped = result.preview.skippedGuidelineDuplicates + result.preview.skippedDecisionDuplicates;
7
+ const summary = result.applied
8
+ ? `Migrated legacy project context: ${guidelinesAdded} guideline${guidelinesAdded === 1 ? "" : "s"}, ${acceptedDecisionsAdded} accepted decision${acceptedDecisionsAdded === 1 ? "" : "s"}, ${duplicatesSkipped} duplicate${duplicatesSkipped === 1 ? "" : "s"} skipped.`
9
+ : "Legacy project context is already canonical; no migration was needed.";
10
+ return {
11
+ changed: result.applied,
12
+ project: result.project,
13
+ legacyProjectContext: {
14
+ migrated: result.applied,
15
+ version: result.preview.version,
16
+ guidelinesAdded,
17
+ acceptedDecisionsAdded,
18
+ duplicatesSkipped,
19
+ clearedFields: result.preview.fieldsClearedOnApply,
20
+ summary,
21
+ },
22
+ };
23
+ }
24
+ const SOURCE_LABELS = {
25
+ "global-rules": "Global rules",
26
+ "before-phase-start": "Before phase start",
27
+ "before-task-start": "Before task start",
28
+ "after-phase-complete": "After phase complete",
29
+ };
30
+ function normalizeComparable(value) {
31
+ return value
32
+ .trim()
33
+ .replace(/^[\s#>*+-]+/, "")
34
+ .replace(/\s+/g, " ")
35
+ .toLocaleLowerCase("en-US");
36
+ }
37
+ function guidelineKeys(content) {
38
+ return new Set(content
39
+ .split(/\r?\n/)
40
+ .map(normalizeComparable)
41
+ .filter(Boolean));
42
+ }
43
+ function workflowEntries(workflowRules) {
44
+ return [
45
+ ...workflowRules.beforePhaseStart.map((text) => ({ source: "before-phase-start", text })),
46
+ ...workflowRules.beforeTaskStart.map((text) => ({ source: "before-task-start", text })),
47
+ ...workflowRules.afterPhaseComplete.map((text) => ({ source: "after-phase-complete", text })),
48
+ ];
49
+ }
50
+ function renderGuidelineAdditions(existing, additions) {
51
+ if (additions.length === 0)
52
+ return existing.trim();
53
+ const lines = ["## Migrated legacy project rules", ""];
54
+ for (const source of Object.keys(SOURCE_LABELS)) {
55
+ const entries = additions.filter((entry) => entry.source === source);
56
+ if (entries.length === 0)
57
+ continue;
58
+ lines.push(`### ${SOURCE_LABELS[source]}`, ...entries.map((entry) => `- ${entry.text}`), "");
59
+ }
60
+ const rendered = lines.join("\n").trim();
61
+ return existing.trim() ? `${existing.trim()}\n\n${rendered}` : rendered;
62
+ }
63
+ function decisionKey(value) {
64
+ if (typeof value === "string")
65
+ return normalizeComparable(value);
66
+ return normalizeComparable(value.decision || value.title);
67
+ }
68
+ function legacyDecisionId(decision) {
69
+ return `legacy-project-decision-${createHash("sha256").update(decisionKey(decision), "utf8").digest("hex").slice(0, 16)}`;
70
+ }
71
+ function legacyDecisionTitle(decision) {
72
+ const firstLine = decision.trim().split(/\r?\n/, 1)[0] ?? decision.trim();
73
+ return firstLine.length <= 100 ? firstLine : `${firstLine.slice(0, 97).trimEnd()}...`;
74
+ }
75
+ export function previewLegacyProjectContextMigration(project) {
76
+ const legacyGuidelines = [
77
+ ...project.globalRules.map((text) => ({ source: "global-rules", text })),
78
+ ...workflowEntries(project.workflowRules),
79
+ ];
80
+ const existingGuidelines = guidelineKeys(project.projectGuidelines.content);
81
+ const seenGuidelines = new Set(existingGuidelines);
82
+ const guidelineAdditions = [];
83
+ let skippedGuidelineDuplicates = 0;
84
+ for (const entry of legacyGuidelines) {
85
+ const text = entry.text.trim();
86
+ const key = normalizeComparable(text);
87
+ if (!key || seenGuidelines.has(key)) {
88
+ skippedGuidelineDuplicates += 1;
89
+ continue;
90
+ }
91
+ seenGuidelines.add(key);
92
+ guidelineAdditions.push({ ...entry, text });
93
+ }
94
+ const seenDecisions = new Set(project.acceptedDecisions.map(decisionKey));
95
+ const acceptedDecisionAdditions = [];
96
+ let skippedDecisionDuplicates = 0;
97
+ for (const legacyDecision of project.decisions) {
98
+ const decision = legacyDecision.trim();
99
+ const key = decisionKey(decision);
100
+ if (!key || seenDecisions.has(key)) {
101
+ skippedDecisionDuplicates += 1;
102
+ continue;
103
+ }
104
+ seenDecisions.add(key);
105
+ acceptedDecisionAdditions.push({
106
+ id: legacyDecisionId(decision),
107
+ title: legacyDecisionTitle(decision),
108
+ decision,
109
+ rationale: "Migrated from the legacy project decisions collection.",
110
+ implementationNotes: "Review and enrich this structured decision when additional rationale or implementation detail is available.",
111
+ });
112
+ }
113
+ const workflowRuleCount = project.workflowRules.beforePhaseStart.length
114
+ + project.workflowRules.beforeTaskStart.length
115
+ + project.workflowRules.afterPhaseComplete.length;
116
+ const fieldsClearedOnApply = [];
117
+ if (project.globalRules.length > 0)
118
+ fieldsClearedOnApply.push("globalRules");
119
+ if (workflowRuleCount > 0)
120
+ fieldsClearedOnApply.push("workflowRules");
121
+ if (project.decisions.length > 0)
122
+ fieldsClearedOnApply.push("decisions");
123
+ return {
124
+ version: LEGACY_PROJECT_CONTEXT_MIGRATION_VERSION,
125
+ hasLegacyContext: fieldsClearedOnApply.length > 0,
126
+ guidelinesChanged: guidelineAdditions.length > 0,
127
+ acceptedDecisionsChanged: acceptedDecisionAdditions.length > 0,
128
+ guidelineAdditions,
129
+ acceptedDecisionAdditions,
130
+ skippedGuidelineDuplicates,
131
+ skippedDecisionDuplicates,
132
+ resultingGuidelinesContent: renderGuidelineAdditions(project.projectGuidelines.content, guidelineAdditions),
133
+ legacyCounts: {
134
+ globalRules: project.globalRules.length,
135
+ workflowRules: workflowRuleCount,
136
+ decisions: project.decisions.length,
137
+ },
138
+ fieldsClearedOnApply,
139
+ };
140
+ }
141
+ export function applyLegacyProjectContextMigration(project, acceptedAt) {
142
+ const preview = previewLegacyProjectContextMigration(project);
143
+ if (!preview.hasLegacyContext)
144
+ return { applied: false, preview, project };
145
+ const acceptedDecisions = [
146
+ ...project.acceptedDecisions,
147
+ ...preview.acceptedDecisionAdditions.map((decision) => ({ ...decision, acceptedAt })),
148
+ ];
149
+ return {
150
+ applied: true,
151
+ preview,
152
+ project: {
153
+ ...project,
154
+ projectGuidelines: {
155
+ ...project.projectGuidelines,
156
+ content: preview.resultingGuidelinesContent,
157
+ },
158
+ globalRules: [],
159
+ workflowRules: {
160
+ beforePhaseStart: [],
161
+ beforeTaskStart: [],
162
+ afterPhaseComplete: [],
163
+ },
164
+ decisions: [],
165
+ acceptedDecisions,
166
+ },
167
+ };
168
+ }
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Session-scoped, ordered context-read enforcement for agent lifecycle operations.
2
+ * Session-scoped context-read enforcement for agent lifecycle operations.
3
3
  *
4
- * The first complete read for a task must be task(full) → phase(full) →
5
- * feature(full), with linked requirements read independently. A persisted
6
- * sessionInfo attestation may satisfy later checks in the same session while
7
- * the entity's context revision remains at or before its attestation timestamp.
4
+ * Task/phase/feature reads and linked requirements remain explicit, but fresh
5
+ * in-session reads may be performed in any order. Persisted sessionInfo
6
+ * attestations may satisfy later checks while the entity's current revision
7
+ * remains at or before the attested timestamp.
8
8
  */
9
9
  type SessionInfoEntry = {
10
10
  sessionId: string;
@@ -15,11 +15,18 @@ type ReadTrackedEntity = {
15
15
  descriptionUpdatedAt?: string;
16
16
  sessionInfo?: SessionInfoEntry[];
17
17
  };
18
+ type ProjectGuidelinesTrackedProject = {
19
+ projectGuidelines?: {
20
+ content?: string;
21
+ updatedAt?: string;
22
+ sessionInfo?: SessionInfoEntry[];
23
+ };
24
+ };
18
25
  export type ContextReadEntityKind = "task" | "phase" | "feature";
19
26
  export type RequiredContextRead = {
20
27
  kind: ContextReadEntityKind;
21
28
  id: string;
22
- state: "missing" | "stale" | "out-of-order";
29
+ state: "missing" | "stale";
23
30
  };
24
31
  export type ContextReadEligibility = {
25
32
  eligible: boolean;
@@ -56,12 +63,15 @@ export declare function markRequirementRead(requirementId: string): void;
56
63
  /** Record that a requirement was explicitly read for an explicit session. */
57
64
  export declare function markRequirementReadForSessionId(sessionId: string, requirementId: string): void;
58
65
  /**
59
- * Combine valid persisted attestations with fresh in-memory reads. The initial
60
- * uncached lineage still requires task phase feature ordering, while a
61
- * stale or missing individual entity can be reread without discarding valid
62
- * parent attestations from the same harness session.
66
+ * Combine valid persisted attestations with fresh in-memory reads. An entity read
67
+ * in the current session satisfies eligibility regardless of sequence order,
68
+ * so agents can read task, phase, and feature in any order; only persisted
69
+ * attestations are checked for revision freshness (stale) and missing entities
70
+ * remain reported as missing.
63
71
  */
64
72
  export declare function contextReadEligibilityForSession(input: SessionContextReadInput): ContextReadEligibility;
73
+ export type ProjectGuidelinesReadState = "not-required" | "missing" | "stale" | "valid";
74
+ export declare function projectGuidelinesReadStateForSession(project: ProjectGuidelinesTrackedProject, sessionId: string): ProjectGuidelinesReadState;
65
75
  /** Return true only when the persisted attestation covers the current revisions. */
66
76
  export declare function hasValidSessionAttestation(input: SessionContextReadInput): boolean;
67
77
  /** Compatibility check for the default session and in-memory reads only. */
@@ -1 +1 @@
1
- {"version":3,"file":"read-tracking.d.ts","sourceRoot":"","sources":["../src/read-tracking.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,KAAK,gBAAgB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE,KAAK,iBAAiB,GAAG;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAClC,CAAC;AAUF,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,YAAY,CAAC,EAAE,KAAK,CAAC,iBAAiB,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AA8CD,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,kEAAkE;AAClE,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAEtF;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAExE;AAED,gEAAgE;AAChE,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAElF;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAEzF;AAED,+DAA+D;AAC/D,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAEhF;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAE/D;AAED,6EAA6E;AAC7E,wBAAgB,+BAA+B,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAE9F;AA+DD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,uBAAuB,GAAG,sBAAsB,CAwCvG;AAED,oFAAoF;AACpF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAElF;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,sBAAsB,CAElH;AAED,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAGtF;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EAAE,EACxB,YAAY,GAAE,KAAK,CAAC,iBAAiB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAM,GAC3D,OAAO,CAGT;AAED,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAErE;AAED,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAQxD;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIzF;AAED,+DAA+D;AAC/D,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,CAMxE;AAED,gCAAgC;AAChC,wBAAgB,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAOzH"}
1
+ {"version":3,"file":"read-tracking.d.ts","sourceRoot":"","sources":["../src/read-tracking.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Session-scoped, ordered context-read enforcement for agent lifecycle operations.
2
+ * Session-scoped context-read enforcement for agent lifecycle operations.
3
3
  *
4
- * The first complete read for a task must be task(full) → phase(full) →
5
- * feature(full), with linked requirements read independently. A persisted
6
- * sessionInfo attestation may satisfy later checks in the same session while
7
- * the entity's context revision remains at or before its attestation timestamp.
4
+ * Task/phase/feature reads and linked requirements remain explicit, but fresh
5
+ * in-session reads may be performed in any order. Persisted sessionInfo
6
+ * attestations may satisfy later checks while the entity's current revision
7
+ * remains at or before the attested timestamp.
8
8
  */
9
9
  const DEFAULT_SESSION_ID = "__default__";
10
10
  const newState = () => ({
@@ -77,19 +77,14 @@ export function markRequirementReadForSessionId(sessionId, requirementId) {
77
77
  }
78
78
  function orderedEligibility(sessionId, taskId, phaseId, featureId) {
79
79
  const state = stateFor(sessionId);
80
- const taskSequence = state.tasks.get(taskId);
81
- if (taskSequence === undefined) {
80
+ if (!state.tasks.has(taskId)) {
82
81
  return { eligible: false, reason: "Read this exact task with full=true first." };
83
82
  }
84
- const phaseSequence = state.phases.get(phaseId);
85
- if (phaseSequence === undefined || phaseSequence <= taskSequence) {
86
- return { eligible: false, reason: "After reading the task, read its parent phase with full=true." };
83
+ if (!state.phases.has(phaseId)) {
84
+ return { eligible: false, reason: "Read this task's parent phase with full=true." };
87
85
  }
88
- if (!featureId)
89
- return { eligible: true, reason: "" };
90
- const featureSequence = state.features.get(featureId);
91
- if (featureSequence === undefined || featureSequence <= phaseSequence) {
92
- return { eligible: false, reason: "After reading the phase, read its parent feature with full=true." };
86
+ if (featureId && !state.features.has(featureId)) {
87
+ return { eligible: false, reason: "Read this phase's parent feature with full=true." };
93
88
  }
94
89
  return { eligible: true, reason: "" };
95
90
  }
@@ -118,13 +113,14 @@ function persistedEligibility(input) {
118
113
  }
119
114
  function requiredReadReason(requiredReads) {
120
115
  const labels = requiredReads.map((read) => `${read.kind} ${read.id} (${read.state})`);
121
- return `Read required context only for: ${labels.join(", ")}. Follow these reads in the listed order, then retry.`;
116
+ return `Read required context only for: ${labels.join(", ")}. Perform only these reads, in any order within the current session, then retry.`;
122
117
  }
123
118
  /**
124
- * Combine valid persisted attestations with fresh in-memory reads. The initial
125
- * uncached lineage still requires task phase feature ordering, while a
126
- * stale or missing individual entity can be reread without discarding valid
127
- * parent attestations from the same harness session.
119
+ * Combine valid persisted attestations with fresh in-memory reads. An entity read
120
+ * in the current session satisfies eligibility regardless of sequence order,
121
+ * so agents can read task, phase, and feature in any order; only persisted
122
+ * attestations are checked for revision freshness (stale) and missing entities
123
+ * remain reported as missing.
128
124
  */
129
125
  export function contextReadEligibilityForSession(input) {
130
126
  const state = stateFor(input.sessionId);
@@ -137,26 +133,23 @@ export function contextReadEligibilityForSession(input) {
137
133
  }
138
134
  const phaseStored = storedReadState(input.phase, input.sessionId, "phase");
139
135
  const phaseSequence = state.phases.get(input.phaseId);
140
- const phaseReadInOrder = phaseSequence !== undefined
141
- && (taskStored === "valid" || (taskSequence !== undefined && phaseSequence > taskSequence));
142
- const phaseReady = phaseStored === "valid" || phaseReadInOrder;
136
+ const phaseReady = phaseStored === "valid" || phaseSequence !== undefined;
143
137
  if (!phaseReady) {
144
138
  requiredReads.push({
145
139
  kind: "phase",
146
140
  id: input.phaseId,
147
- state: phaseStored === "stale" ? "stale" : phaseSequence !== undefined ? "out-of-order" : "missing",
141
+ state: phaseStored === "stale" ? "stale" : "missing",
148
142
  });
149
143
  }
150
144
  if (input.featureId) {
151
145
  const featureStored = storedReadState(input.feature, input.sessionId, "feature");
152
146
  const featureSequence = state.features.get(input.featureId);
153
- const featureReadInOrder = featureSequence !== undefined
154
- && (phaseStored === "valid" || (phaseReadInOrder && phaseSequence !== undefined && featureSequence > phaseSequence));
155
- if (featureStored !== "valid" && !featureReadInOrder) {
147
+ const featureReady = featureStored === "valid" || featureSequence !== undefined;
148
+ if (!featureReady) {
156
149
  requiredReads.push({
157
150
  kind: "feature",
158
151
  id: input.featureId,
159
- state: featureStored === "stale" ? "stale" : featureSequence !== undefined ? "out-of-order" : "missing",
152
+ state: featureStored === "stale" ? "stale" : "missing",
160
153
  });
161
154
  }
162
155
  }
@@ -164,6 +157,19 @@ export function contextReadEligibilityForSession(input) {
164
157
  return { eligible: true, reason: "" };
165
158
  return { eligible: false, reason: requiredReadReason(requiredReads), requiredReads };
166
159
  }
160
+ export function projectGuidelinesReadStateForSession(project, sessionId) {
161
+ const guidelines = project.projectGuidelines;
162
+ const content = guidelines?.content?.trim() ?? "";
163
+ if (!content)
164
+ return "not-required";
165
+ const entry = guidelines?.sessionInfo?.find((candidate) => candidate.sessionId === sessionId);
166
+ if (!entry)
167
+ return "missing";
168
+ const updatedAt = guidelines?.updatedAt?.trim() ?? "";
169
+ if (updatedAt && updatedAt > entry.createdAt)
170
+ return "stale";
171
+ return "valid";
172
+ }
167
173
  /** Return true only when the persisted attestation covers the current revisions. */
168
174
  export function hasValidSessionAttestation(input) {
169
175
  return persistedEligibility(input);
@@ -208,7 +214,7 @@ export function parentReadAdvisory(featureId, phaseId) {
208
214
  const state = stateFor(DEFAULT_SESSION_ID);
209
215
  if (state.phases.has(phaseId) && (!featureId || state.features.has(featureId)))
210
216
  return "";
211
- return "\n\n⚠️ READ REQUIRED before proceeding: read the parent phase and feature with full=true.";
217
+ return "\n\n⚠️ READ REQUIRED before proceeding: read only the missing parent phase and feature with full=true.";
212
218
  }
213
219
  /** Advisory text for the separate linked-requirements gate. */
214
220
  export function requirementReadAdvisory(requirementIds) {
@@ -1 +1 @@
1
- {"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../src/recap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,KAAK,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,GAAE,YAAiB,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuOhH"}
1
+ {"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../src/recap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,KAAK,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,GAAE,YAAiB,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6OhH"}
package/dist/recap.js CHANGED
@@ -67,6 +67,7 @@ export async function buildRecap(st, web = {}, opts = {}) {
67
67
  lines.push(italian ? "## Ripresa planner" : "## Planner recap");
68
68
  const name = plan.project.name || "(unnamed project)";
69
69
  lines.push(`${italian ? "Progetto" : "Project"}: ${name}${plan.project.goal ? " — " + plan.project.goal : ""}`);
70
+ lines.push("", "## Project Guidelines", plan.project.projectGuidelines.content.trim() || "No project guidelines set.");
70
71
  lines.push(italian
71
72
  ? `Avanzamento: feature ${doneF}/${totalF} completate (${activeF} attive) · fasi ${doneP}/${totalP} completate (${activeP} attive) · task ${doneT}/${totalT} completati (${activeT} attivi, ${checkpointedT} con checkpoint)`
72
73
  : `Progress: Features ${doneF}/${totalF} done (${activeF} active) · Phases ${doneP}/${totalP} done (${activeP} active) · Tasks ${doneT}/${totalT} done (${activeT} active, ${checkpointedT} with checkpoints)`);
package/dist/refs.d.ts CHANGED
@@ -11,7 +11,12 @@
11
11
  * - Compos: "P002(F001)" -> phase.number with parent feature validation
12
12
  * - Title: exact match, then includes (backward-compat fallback)
13
13
  */
14
- import type { Phase, Feature } from "./schema.js";
14
+ import type { Phase, Feature, Idea } from "./schema.js";
15
+ /**
16
+ * Resolve an idea by UUID, I00x number, shortId, exact title, then title
17
+ * inclusion. Ideas are top-level and never require feature/phase context.
18
+ */
19
+ export declare function findIdeaByRef(ideas: Idea[], ref: string): Idea | undefined;
15
20
  /**
16
21
  * Resolve a phase reference to a Phase. Returns `undefined` when not found or
17
22
  * when a composite (F00x) parent does not match the phase's featureId.
@@ -1 +1 @@
1
- {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../src/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAKlD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV,KAAK,GAAG,SAAS,CAgCnB;AACD;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAiBxC,wBAAgB,aAAa,CAC3B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,CA2D1C"}
1
+ {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../src/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAMxD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAmB1E;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV,KAAK,GAAG,SAAS,CAgCnB;AACD;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAiBxC,wBAAgB,aAAa,CAC3B,MAAM,EAAE,KAAK,EAAE,EACf,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,CA2D1C"}