fix(self-feedback): JSONL header is JSON-valid meta marker, not # comment
Some checks are pending
CI / detect-changes (push) Waiting to run
CI / docs-check (push) Blocked by required conditions
CI / lint (push) Blocked by required conditions
CI / build (push) Blocked by required conditions
CI / integration-tests (push) Blocked by required conditions
CI / windows-portability (push) Blocked by required conditions
CI / rtk-portability (linux, blacksmith-4vcpu-ubuntu-2404) (push) Blocked by required conditions
CI / rtk-portability (macos, macos-15) (push) Blocked by required conditions
CI / rtk-portability (windows, blacksmith-4vcpu-windows-2025) (push) Blocked by required conditions
Some checks are pending
CI / detect-changes (push) Waiting to run
CI / docs-check (push) Blocked by required conditions
CI / lint (push) Blocked by required conditions
CI / build (push) Blocked by required conditions
CI / integration-tests (push) Blocked by required conditions
CI / windows-portability (push) Blocked by required conditions
CI / rtk-portability (linux, blacksmith-4vcpu-ubuntu-2404) (push) Blocked by required conditions
CI / rtk-portability (macos, macos-15) (push) Blocked by required conditions
CI / rtk-portability (windows, blacksmith-4vcpu-windows-2025) (push) Blocked by required conditions
Phase 2 (216b1d43f) wrote "# generated from .sf/sf.db ..." as line 1 of
.sf/self-feedback.jsonl. readJsonl tolerated it via try/catch around
JSON.parse, but the doctor's stricter JSONL syntax check flagged it as
"invalid jsonl syntax: line 1: Unexpected token '#'".
Replace the # comment with a JSON-valid meta marker:
{"_meta":"generated from .sf/sf.db","_warning":"do not edit directly; use the resolve_issue tool or sf headless triage --apply"}
readJsonl now skips entries carrying `_meta` so downstream consumers
don't see the marker as a self-feedback record. Tests updated to match
the new marker shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
216b1d43f1
commit
8b4123cccc
2 changed files with 39 additions and 22 deletions
|
|
@ -56,8 +56,14 @@ const SELF_FEEDBACK_HEADER =
|
|||
"durable source of truth is `.sf/sf.db`.\n\n" +
|
||||
"Blocking entries (severity high+) remain active until an sf fix explicitly\n" +
|
||||
"marks them resolved with evidence.\n\n";
|
||||
const JSONL_GENERATED_COMMENT =
|
||||
"# generated from .sf/sf.db — do not edit directly; use the resolve_issue tool or sf headless triage --apply";
|
||||
// JSONL marker is a valid JSON object so strict JSONL parsers (e.g. the
|
||||
// doctor's syntax check) accept line 1. The _meta sentinel keeps the
|
||||
// "do not hand-edit" intent visible to anyone opening the file.
|
||||
const JSONL_GENERATED_COMMENT = JSON.stringify({
|
||||
_meta: "generated from .sf/sf.db",
|
||||
_warning:
|
||||
"do not edit directly; use the resolve_issue tool or sf headless triage --apply",
|
||||
});
|
||||
const RECENT_RESOLVED_MARKDOWN_LIMIT = 20;
|
||||
const MARKDOWN_DETAIL_CHAR_LIMIT = 2_000;
|
||||
const SELF_FEEDBACK_SCHEMA_VERSION = 1;
|
||||
|
|
@ -225,7 +231,12 @@ function readJsonl(path) {
|
|||
for (const line of readFileSync(path, "utf-8").split("\n")) {
|
||||
if (!line.trim()) continue;
|
||||
try {
|
||||
out.push(JSON.parse(line));
|
||||
const parsed = JSON.parse(line);
|
||||
// Skip meta header lines emitted by regenerateSelfFeedbackJsonl
|
||||
// — those are generator metadata, not self-feedback entries.
|
||||
// Entries always carry an `id` string; meta lines have `_meta`.
|
||||
if (parsed && typeof parsed === "object" && parsed._meta) continue;
|
||||
out.push(parsed);
|
||||
} catch {
|
||||
/* skip malformed lines */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -780,8 +780,10 @@ test("markResolved_regenerates_jsonl_from_db_with_resolved_state", () => {
|
|||
)
|
||||
.split("\n")
|
||||
.filter((l) => l.trim());
|
||||
// First non-empty line is the generated comment (starts with #)
|
||||
assert.ok(lines[0].startsWith("#"), "expected generated comment header");
|
||||
// First non-empty line is the generated meta marker (valid JSON object so
|
||||
// strict JSONL parsers like the doctor accept it; carries _meta sentinel).
|
||||
const meta = JSON.parse(lines[0]);
|
||||
assert.ok(meta._meta, "expected JSONL meta header on line 1");
|
||||
// Second line is the resolved entry
|
||||
const entry = JSON.parse(lines[1]);
|
||||
assert.equal(entry.id, result.entry.id);
|
||||
|
|
@ -982,16 +984,16 @@ test("markResolved_db_only_entry_populates_jsonl_and_markdown_after_resolution",
|
|||
join(project, ".sf", "self-feedback.jsonl"),
|
||||
"utf-8",
|
||||
);
|
||||
// Comment header must be present
|
||||
// Generated meta marker must be present on line 1 (JSON object with _meta key)
|
||||
assert.ok(
|
||||
jsonlContent.includes("# generated from .sf/sf.db"),
|
||||
"JSONL must have generated comment header",
|
||||
jsonlContent.includes('"_meta":"generated from .sf/sf.db"'),
|
||||
"JSONL must have generated meta header",
|
||||
);
|
||||
// Entry must appear as resolved
|
||||
// Entry must appear as resolved — filter out the _meta header line
|
||||
const jsonlLines = jsonlContent.split("\n").filter((l) => {
|
||||
try {
|
||||
JSON.parse(l);
|
||||
return true;
|
||||
const parsed = JSON.parse(l);
|
||||
return parsed && typeof parsed === "object" && !parsed._meta;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1045,9 +1047,14 @@ test("backfillSelfFeedbackJsonl_makes_jsonl_equal_to_db_render", () => {
|
|||
join(project, ".sf", "self-feedback.jsonl"),
|
||||
"utf-8",
|
||||
);
|
||||
const firstEntryLines = firstContent
|
||||
.split("\n")
|
||||
.filter((l) => { try { JSON.parse(l); return true; } catch { return false; } });
|
||||
const firstEntryLines = firstContent.split("\n").filter((l) => {
|
||||
try {
|
||||
const parsed = JSON.parse(l);
|
||||
return parsed && typeof parsed === "object" && !parsed._meta;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
assert.equal(firstEntryLines.length, 2, "JSONL must have both DB entries");
|
||||
|
||||
// Second backfill is idempotent — exact same content
|
||||
|
|
@ -1064,8 +1071,9 @@ test("backfillSelfFeedbackJsonl_makes_jsonl_equal_to_db_render", () => {
|
|||
});
|
||||
|
||||
test("regenerateSelfFeedbackJsonl_includes_generated_comment_header", () => {
|
||||
// The JSONL file must start with a # comment line marking it as generated.
|
||||
// readJsonl already skips non-JSON lines (the comment) gracefully.
|
||||
// The JSONL file must start with a JSON-valid meta marker (so strict
|
||||
// JSONL parsers like the doctor accept the file). readJsonl skips entries
|
||||
// carrying the _meta sentinel so consumers don't see them as records.
|
||||
const project = makeForgeProject();
|
||||
recordSelfFeedback(
|
||||
{ kind: "gap:header-test", severity: "low", summary: "header check" },
|
||||
|
|
@ -1080,13 +1088,11 @@ test("regenerateSelfFeedbackJsonl_includes_generated_comment_header", () => {
|
|||
"utf-8",
|
||||
);
|
||||
const firstLine = content.split("\n")[0];
|
||||
const meta = JSON.parse(firstLine);
|
||||
assert.ok(meta._meta, "JSONL first line must be a JSON object with _meta key");
|
||||
assert.ok(
|
||||
firstLine.startsWith("#"),
|
||||
"JSONL first line must be a # comment (generated header)",
|
||||
);
|
||||
assert.ok(
|
||||
firstLine.includes("generated from .sf/sf.db"),
|
||||
"generated header must mention sf.db",
|
||||
meta._meta.includes("generated from .sf/sf.db"),
|
||||
"_meta must mention sf.db",
|
||||
);
|
||||
assert.ok(
|
||||
firstLine.includes("resolve_issue"),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue