@vibecompass/vibecompass 0.13.0 → 0.13.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/CHANGELOG.md +11 -0
- package/package.json +1 -1
- package/src/project-memory.js +117 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.13.1 - 2026-09-11
|
|
6
|
+
|
|
7
|
+
- Add strict backward compatibility for intentional legacy recovery checkpoints
|
|
8
|
+
at root `sessions/YYYY-MM-DD.md` paths with an exact
|
|
9
|
+
`# Recovery checkpoint — YYYY-MM-DD — <title>` H1; project them with their
|
|
10
|
+
matching `session_date`, title, and
|
|
11
|
+
`session_number: null` without inventing numbered-session chronology.
|
|
12
|
+
- Preserve fail-closed behavior for malformed, mismatched, untitled, or nested
|
|
13
|
+
recovery checkpoints and all existing numbered-session behavior, with focused
|
|
14
|
+
parser, manifest, read-model, and session-lifecycle regression coverage.
|
|
15
|
+
|
|
5
16
|
## 0.13.0 - 2026-07-11
|
|
6
17
|
|
|
7
18
|
- Architecture docs are now explicitly current-state contracts (D-292/D-293):
|
package/package.json
CHANGED
package/src/project-memory.js
CHANGED
|
@@ -665,11 +665,34 @@ function parseSessionDocument(options) {
|
|
|
665
665
|
const h1Match = options.content.match(/^#\s+Session(?:\s+—\s+(.+))?$/m);
|
|
666
666
|
|
|
667
667
|
if (!filenameMatch && !h1Match) {
|
|
668
|
+
const recoveryCheckpoint = parseRecoveryCheckpointIdentity(options);
|
|
669
|
+
if (recoveryCheckpoint.candidate) {
|
|
670
|
+
if (recoveryCheckpoint.error) {
|
|
671
|
+
errors.push(recoveryCheckpoint.error);
|
|
672
|
+
return { kind: 'session', extracted: null, warnings, errors };
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
warnings.push(
|
|
676
|
+
createWarning(
|
|
677
|
+
'session-filename-mismatch',
|
|
678
|
+
'Recovery checkpoint uses the supported legacy sessions/YYYY-MM-DD.md filename instead of the recommended YYYY-MM-DD-N-display-title.md pattern.',
|
|
679
|
+
),
|
|
680
|
+
);
|
|
681
|
+
appendMissingSessionSectionWarnings(options.content, warnings);
|
|
682
|
+
|
|
683
|
+
return {
|
|
684
|
+
kind: 'session',
|
|
685
|
+
extracted: recoveryCheckpoint.extracted,
|
|
686
|
+
warnings,
|
|
687
|
+
errors,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
668
691
|
errors.push(
|
|
669
692
|
createError(
|
|
670
693
|
options.relativePath,
|
|
671
694
|
'session-unrecognized',
|
|
672
|
-
'Session note must use the session filename pattern
|
|
695
|
+
'Session note must use the session filename pattern, a "# Session" heading, or the strict legacy recovery-checkpoint form.',
|
|
673
696
|
),
|
|
674
697
|
);
|
|
675
698
|
return { kind: 'session', extracted: null, warnings, errors };
|
|
@@ -684,14 +707,7 @@ function parseSessionDocument(options) {
|
|
|
684
707
|
);
|
|
685
708
|
}
|
|
686
709
|
|
|
687
|
-
|
|
688
|
-
for (const section of RECOMMENDED_SESSION_SECTIONS) {
|
|
689
|
-
if (!sections.has(section.toLowerCase())) {
|
|
690
|
-
warnings.push(
|
|
691
|
-
createWarning('session-missing-section', `Session note is missing recommended section "## ${section}".`),
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
710
|
+
appendMissingSessionSectionWarnings(options.content, warnings);
|
|
695
711
|
|
|
696
712
|
const title = extractSessionTitle(options.content, filenameMatch);
|
|
697
713
|
|
|
@@ -707,6 +723,98 @@ function parseSessionDocument(options) {
|
|
|
707
723
|
};
|
|
708
724
|
}
|
|
709
725
|
|
|
726
|
+
function parseRecoveryCheckpointIdentity(options) {
|
|
727
|
+
const filenameMatch = options.relativePath.match(/^sessions\/(\d{4}-\d{2}-\d{2})\.md$/);
|
|
728
|
+
const firstLine = options.content.split(/\r?\n/, 1)[0];
|
|
729
|
+
const headingCandidate = /^# Recovery checkpoint(?:\s|$)/.test(firstLine);
|
|
730
|
+
const candidate = Boolean(filenameMatch) || headingCandidate;
|
|
731
|
+
|
|
732
|
+
if (!candidate) {
|
|
733
|
+
return { candidate: false, extracted: null, error: null };
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (!filenameMatch) {
|
|
737
|
+
return {
|
|
738
|
+
candidate: true,
|
|
739
|
+
extracted: null,
|
|
740
|
+
error: createError(
|
|
741
|
+
options.relativePath,
|
|
742
|
+
'session-recovery-checkpoint-path-invalid',
|
|
743
|
+
'Legacy recovery checkpoints must be root session files named sessions/YYYY-MM-DD.md.',
|
|
744
|
+
),
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const headingMatch = firstLine.match(
|
|
749
|
+
/^# Recovery checkpoint — (\d{4}-\d{2}-\d{2}) — (\S(?:.*\S)?)$/,
|
|
750
|
+
);
|
|
751
|
+
if (!headingMatch) {
|
|
752
|
+
return {
|
|
753
|
+
candidate: true,
|
|
754
|
+
extracted: null,
|
|
755
|
+
error: createError(
|
|
756
|
+
options.relativePath,
|
|
757
|
+
'session-recovery-checkpoint-heading-invalid',
|
|
758
|
+
'Legacy recovery checkpoints require "# Recovery checkpoint — YYYY-MM-DD — <nonempty title>".',
|
|
759
|
+
),
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const filenameDate = filenameMatch[1];
|
|
764
|
+
const headingDate = headingMatch[1];
|
|
765
|
+
if (!isValidIsoCalendarDate(filenameDate) || !isValidIsoCalendarDate(headingDate)) {
|
|
766
|
+
return {
|
|
767
|
+
candidate: true,
|
|
768
|
+
extracted: null,
|
|
769
|
+
error: createError(
|
|
770
|
+
options.relativePath,
|
|
771
|
+
'session-recovery-checkpoint-date-invalid',
|
|
772
|
+
'Legacy recovery checkpoint dates must be valid ISO calendar dates.',
|
|
773
|
+
),
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (filenameDate !== headingDate) {
|
|
778
|
+
return {
|
|
779
|
+
candidate: true,
|
|
780
|
+
extracted: null,
|
|
781
|
+
error: createError(
|
|
782
|
+
options.relativePath,
|
|
783
|
+
'session-recovery-checkpoint-date-mismatch',
|
|
784
|
+
`Legacy recovery checkpoint filename date ${filenameDate} does not match heading date ${headingDate}.`,
|
|
785
|
+
),
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return {
|
|
790
|
+
candidate: true,
|
|
791
|
+
extracted: {
|
|
792
|
+
title: headingMatch[2].trim(),
|
|
793
|
+
session_date: filenameDate,
|
|
794
|
+
// A checkpoint records continuity inside an open lane; assigning a
|
|
795
|
+
// number would invent a close-session chronology that never occurred.
|
|
796
|
+
session_number: null,
|
|
797
|
+
},
|
|
798
|
+
error: null,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function appendMissingSessionSectionWarnings(content, warnings) {
|
|
803
|
+
const sections = extractLevelTwoSections(content);
|
|
804
|
+
for (const section of RECOMMENDED_SESSION_SECTIONS) {
|
|
805
|
+
if (!sections.has(section.toLowerCase())) {
|
|
806
|
+
warnings.push(
|
|
807
|
+
createWarning('session-missing-section', `Session note is missing recommended section "## ${section}".`),
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function isValidIsoCalendarDate(value) {
|
|
814
|
+
const parsed = new Date(`${value}T00:00:00.000Z`);
|
|
815
|
+
return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
|
|
816
|
+
}
|
|
817
|
+
|
|
710
818
|
function extractDecisionEntries(content) {
|
|
711
819
|
const matches = [...content.matchAll(/^###\s+D-(\d{3,})\b.*$/gm)];
|
|
712
820
|
|