Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions internal/writer/footer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,23 @@ func whatIsBlock(opts FooterOptions) string {
func HasMarker(content []byte) bool {
return strings.Contains(string(content), MarkerStart)
}

// stripGeneratedMarker 把首个 `<!-- Generated by stdagent ... -->` 注释整体替换为稳定占位符
//
// 用于 writer.Apply 内容等价性比较:marker 含 version + timestamp,每次 sync 都会变,
// 不归一化会导致即使源文档未变也整文件重写,污染 git diff。
//
// 仅替换首个 MarkerStart 注释;MarkerEnd 不带变量值无需处理。
func stripGeneratedMarker(content []byte) []byte {
s := string(content)
start := strings.Index(s, MarkerStart)
if start < 0 {
return content
}
rel := strings.Index(s[start:], "-->")
if rel < 0 {
return content
}
end := start + rel + len("-->")
return []byte(s[:start] + "<!-- stdagent-marker -->" + s[end:])
}
8 changes: 8 additions & 0 deletions internal/writer/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ func (w *Writer) Apply(plan *Plan) (written, skipped int, err error) {
skipped++
continue
}
// marker 含每次 sync 不同的 timestamp(footer.HeaderComment 注入 GeneratedAt),
// 归一化 marker 后再比一次:源文档未变则不重写,避免污染 git diff / mtime。
if len(exist) > 0 && bytes.Equal(stripGeneratedMarker(exist), stripGeneratedMarker(op.Content)) {
op.Skip = true
op.Reason = "unchanged"
skipped++
continue
}
if w.DryRun {
continue
}
Expand Down
67 changes: 67 additions & 0 deletions internal/writer/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,73 @@ func TestApplyMixedWrittenAndSkipped(t *testing.T) {
}
}

func TestApplySkipsWhenOnlyMarkerTimestampDiffers(t *testing.T) {
tmp := t.TempDir()
w := NewWriter(tmp, false)

oldHeader := "<!-- Generated by stdagent v0.1.0 at 2026-01-01T00:00:00Z. Source: .stdai/standards/rules/style.md. Run `stdagent sync` to regenerate. Do not edit by hand. -->\n"
newHeader := "<!-- Generated by stdagent v0.1.0 at 2026-05-10T08:32:11Z. Source: .stdai/standards/rules/style.md. Run `stdagent sync` to regenerate. Do not edit by hand. -->\n"
body := "# Style\n\nbody content\n<!-- /Generated by stdagent -->\n"
mustWrite(t, filepath.Join(tmp, "out.md"), oldHeader+body)

statBefore, _ := os.Stat(filepath.Join(tmp, "out.md"))

plan := &Plan{
Files: []FileOp{
{Path: "out.md", Content: []byte(newHeader + body), Marker: true},
},
}
written, skipped, err := w.Apply(plan)
if err != nil {
t.Fatal(err)
}
if written != 0 || skipped != 1 {
t.Errorf("written=%d skipped=%d, want 0/1", written, skipped)
}
if !plan.Files[0].Skip || plan.Files[0].Reason != "unchanged" {
t.Errorf("op = %+v", plan.Files[0])
}
statAfter, _ := os.Stat(filepath.Join(tmp, "out.md"))
if !statBefore.ModTime().Equal(statAfter.ModTime()) {
t.Errorf("mtime should not change: before=%v after=%v", statBefore.ModTime(), statAfter.ModTime())
}
got, _ := os.ReadFile(filepath.Join(tmp, "out.md"))
if string(got) != oldHeader+body {
t.Errorf("file content should be untouched, got %q", got)
}
}

func TestApplyWritesWhenBodyChangesEvenIfMarkerSame(t *testing.T) {
tmp := t.TempDir()
w := NewWriter(tmp, false)
header := "<!-- Generated by stdagent v0.1.0 at 2026-01-01T00:00:00Z. Source: .stdai/standards/rules/style.md. Run `stdagent sync` to regenerate. Do not edit by hand. -->\n"
mustWrite(t, filepath.Join(tmp, "out.md"), header+"# Old\n")

plan := &Plan{
Files: []FileOp{
{Path: "out.md", Content: []byte(header + "# New\n"), Marker: true},
},
}
written, skipped, _ := w.Apply(plan)
if written != 1 || skipped != 0 {
t.Errorf("written=%d skipped=%d, want 1/0", written, skipped)
}
}

func TestStripGeneratedMarker(t *testing.T) {
src := []byte("<!-- Generated by stdagent v0.1.0 at 2026-05-10T08:32:11Z. Source: x. -->\nbody\n")
got := stripGeneratedMarker(src)
want := "<!-- stdagent-marker -->\nbody\n"
if string(got) != want {
t.Errorf("stripGeneratedMarker = %q, want %q", got, want)
}

plain := []byte("# no marker here\nbody\n")
if string(stripGeneratedMarker(plain)) != string(plain) {
t.Error("content without marker should be returned as-is")
}
}

func TestApplySkipPreSet(t *testing.T) {
tmp := t.TempDir()
w := NewWriter(tmp, false)
Expand Down
Loading