Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add prompt notes to track LLM context on commits #754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jonnii wants to merge 2 commits into main
base: main
Choose a base branch
Loading
from jonnii/20260220125253/add-prompt-notes-to-track-LLM-context-on-commits
Open
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
196 changes: 196 additions & 0 deletions internal/actions/notes/notes.go
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// Package notes implements the stackit notes command for managing contextual notes on commits.
package notes

import (
"encoding/json"
"fmt"

"github.com/getstackit/stackit/internal/app"
"github.com/getstackit/stackit/internal/errors"
"github.com/getstackit/stackit/internal/git"
"github.com/getstackit/stackit/internal/output"
"github.com/getstackit/stackit/internal/tui/style"
)

// ShowOptions contains options for the show subcommand.
type ShowOptions struct {
Commit string // Commit to show note for (defaults to HEAD)
Namespace string // Namespace to read (defaults to story)
}

// AddOptions contains options for the add subcommand.
type AddOptions struct {
Namespace string
Prompt string
Summary string
Model string
Memory []string
JSON string // Raw JSON alternative to individual flags
}

// LogOptions contains options for the log subcommand.
type LogOptions struct {
Namespace string
}

// Show displays the note for a commit.
func Show(ctx *app.Context, opts ShowOptions) error {
out := ctx.Output

commit := opts.Commit
if commit == "" {
rev, err := ctx.Git().GetCurrentRevision(ctx.Context)

Check failure on line 42 in internal/actions/notes/notes.go

View workflow job for this annotation

GitHub Actions / Lint

use of `ctx.Git().GetCurrentRevision` forbidden because "Do not reach through Engine.Git()/Context.Git() to the raw git runner. Add or use an engine method for domain operations. The only allowed uses are passing the runner to integration helpers (github, etc.), which must be annotated with //nolint:forbidigo and a reason." (forbidigo)
if err != nil {
return fmt.Errorf("failed to get current revision: %w", err)
}
commit = rev
}

namespace := opts.Namespace
if namespace == "" {
namespace = git.DefaultNotesNamespace
}

note, err := ctx.Git().ShowPromptNote(ctx.Context, commit, namespace)

Check failure on line 54 in internal/actions/notes/notes.go

View workflow job for this annotation

GitHub Actions / Lint

use of `ctx.Git().ShowPromptNote` forbidden because "Do not reach through Engine.Git()/Context.Git() to the raw git runner. Add or use an engine method for domain operations. The only allowed uses are passing the runner to integration helpers (github, etc.), which must be annotated with //nolint:forbidigo and a reason." (forbidigo)
if err != nil {
return fmt.Errorf("failed to read note: %w", err)
}

if note == nil {
shortSHA := commit
if len(shortSHA) > 7 {
shortSHA = shortSHA[:7]
}
out.Info("No %s note on %s.", style.ColorDim(namespace), style.ColorDim(shortSHA))
return nil
}

printNote(out, note)
return nil
}

// Add creates or replaces a note on HEAD.
func Add(ctx *app.Context, opts AddOptions) error {
out := ctx.Output
g := ctx.Git()
namespace := opts.Namespace
if namespace == "" {
namespace = git.DefaultNotesNamespace
}

rev, err := g.GetCurrentRevision(ctx.Context)
if err != nil {
return fmt.Errorf("failed to get current revision: %w", err)
}

var note git.PromptNote

if opts.JSON != "" {
if err := json.Unmarshal([]byte(opts.JSON), &note); err != nil {
return fmt.Errorf("failed to parse JSON: %w", err)
}
} else {
if opts.Prompt == "" {
return fmt.Errorf("--prompt is required")
}
note = git.PromptNote{
Prompt: opts.Prompt,
Summary: opts.Summary,
Model: opts.Model,
Memory: opts.Memory,
}
}

// Ensure notes.rewriteRef is configured so notes survive rebase
if err := g.EnsureNotesRewriteConfigured(); err != nil {
out.Debug("Failed to configure notes.rewriteRef: %v", err)
}

if err := g.AddPromptNote(ctx.Context, rev, namespace, &note); err != nil {
return err
}

shortSHA := rev
if len(shortSHA) > 7 {
shortSHA = shortSHA[:7]
}
out.Info("Added %s note to %s.", style.ColorDim(namespace), style.ColorDim(shortSHA))
return nil
}

// Log shows commits on the current branch with notes from the requested namespace.
func Log(ctx *app.Context, opts LogOptions) error {
eng := ctx.Engine
out := ctx.Output
namespace := opts.Namespace
if namespace == "" {
namespace = git.DefaultNotesNamespace
}

currentBranch := eng.CurrentBranch()
if currentBranch == nil {
return errors.ErrNotOnBranch
}

// Get parent branch for commit range
parent := currentBranch.GetParent()
if parent == nil {
return fmt.Errorf("branch %s has no parent; cannot determine commit range", currentBranch.GetName())
}

entries, err := ctx.Git().LogWithNotes(ctx.Context, parent.GetName(), currentBranch.GetName(), namespace)

Check failure on line 141 in internal/actions/notes/notes.go

View workflow job for this annotation

GitHub Actions / Lint

use of `ctx.Git().LogWithNotes` forbidden because "Do not reach through Engine.Git()/Context.Git() to the raw git runner. Add or use an engine method for domain operations. The only allowed uses are passing the runner to integration helpers (github, etc.), which must be annotated with //nolint:forbidigo and a reason." (forbidigo)
if err != nil {
return err
}

if len(entries) == 0 {
out.Info("No commits between %s and %s.", parent.GetName(), currentBranch.GetName())
return nil
}

for _, entry := range entries {
shortSHA := entry.SHA
if len(shortSHA) > 7 {
shortSHA = shortSHA[:7]
}
out.Info("%s %s", style.ColorDim(shortSHA), entry.Subject)
if entry.Note != nil {
out.Info(" Prompt: %s", entry.Note.Prompt)
if entry.Note.Summary != "" {
out.Info(" Summary: %s", entry.Note.Summary)
}
} else {
out.Info(" %s", style.ColorDim("(no note in namespace "+namespace+")"))
}
}

return nil
}

// Push pushes notes to the remote.
func Push(ctx *app.Context) error {
out := ctx.Output

if err := ctx.Git().PushNotes(ctx.Context); err != nil {

Check failure on line 174 in internal/actions/notes/notes.go

View workflow job for this annotation

GitHub Actions / Lint

use of `ctx.Git().PushNotes` forbidden because "Do not reach through Engine.Git()/Context.Git() to the raw git runner. Add or use an engine method for domain operations. The only allowed uses are passing the runner to integration helpers (github, etc.), which must be annotated with //nolint:forbidigo and a reason." (forbidigo)
return err
}

out.Info("Pushed notes to remote.")
return nil
}

func printNote(out output.Output, note *git.PromptNote) {
out.Info("Prompt: %s", note.Prompt)
if note.Summary != "" {
out.Info("Summary: %s", note.Summary)
}
if note.Model != "" {
out.Info("Model: %s", note.Model)
}
if len(note.Memory) > 0 {
out.Info("Memory:")
for _, m := range note.Memory {
out.Info(" - %s", m)
}
}
}
6 changes: 6 additions & 0 deletions internal/actions/submit/submit.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,12 @@
}
}

// Push prompt notes alongside branches
if err := ctx.Git().PushNotes(ctx.Context); err != nil {

Check failure on line 730 in internal/actions/submit/submit.go

View workflow job for this annotation

GitHub Actions / Lint

use of `ctx.Git().PushNotes` forbidden because "Do not reach through Engine.Git()/Context.Git() to the raw git runner. Add or use an engine method for domain operations. The only allowed uses are passing the runner to integration helpers (github, etc.), which must be annotated with //nolint:forbidigo and a reason." (forbidigo)
ctx.Output.Debug("Failed to push prompt notes: %v", err)
// Non-fatal: prompt notes push failure shouldn't fail the submit
}

return nil
}

Expand Down
7 changes: 7 additions & 0 deletions internal/actions/sync/metadata_sync.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@
if err := eng.ConfigureStackMetadataSync(ctx.Context); err != nil {
out.Debug("Failed to configure stack metadata refspec: %v", err)
}
// Configure prompt notes refspec and rewrite config
if err := ctx.Git().EnsureNotesRefspecConfigured(); err != nil {

Check failure on line 48 in internal/actions/sync/metadata_sync.go

View workflow job for this annotation

GitHub Actions / Lint

use of `ctx.Git().EnsureNotesRefspecConfigured` forbidden because "Do not reach through Engine.Git()/Context.Git() to the raw git runner. Add or use an engine method for domain operations. The only allowed uses are passing the runner to integration helpers (github, etc.), which must be annotated with //nolint:forbidigo and a reason." (forbidigo)
out.Debug("Failed to configure notes refspec: %v", err)
}
if err := ctx.Git().EnsureNotesRewriteConfigured(); err != nil {

Check failure on line 51 in internal/actions/sync/metadata_sync.go

View workflow job for this annotation

GitHub Actions / Lint

use of `ctx.Git().EnsureNotesRewriteConfigured` forbidden because "Do not reach through Engine.Git()/Context.Git() to the raw git runner. Add or use an engine method for domain operations. The only allowed uses are passing the runner to integration helpers (github, etc.), which must be annotated with //nolint:forbidigo and a reason." (forbidigo)
out.Debug("Failed to configure notes rewrite: %v", err)
}
ctx.Logger.Info("configure remote metadata sync completed durationMs=%d", time.Since(configStart).Milliseconds())

// Load remote metadata into cache
Expand Down
6 changes: 6 additions & 0 deletions internal/actions/sync/sync.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@
if metadataFetchErr != nil {
ctx.Logger.Debug("fetch metadata refs failed (non-fatal) error=%v", metadataFetchErr)
}
// Fetch prompt notes
notesFetchStart := time.Now()
if err := ctx.Git().FetchNotes(gctx); err != nil {

Check failure on line 140 in internal/actions/sync/sync.go

View workflow job for this annotation

GitHub Actions / Lint

use of `ctx.Git().FetchNotes` forbidden because "Do not reach through Engine.Git()/Context.Git() to the raw git runner. Add or use an engine method for domain operations. The only allowed uses are passing the runner to integration helpers (github, etc.), which must be annotated with //nolint:forbidigo and a reason." (forbidigo)
ctx.Logger.Debug("fetch prompt notes failed (non-fatal) error=%v", err)
}
ctx.Logger.Info("fetch prompt notes completed durationMs=%v", time.Since(notesFetchStart).Milliseconds())
return nil
})

Expand Down
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ Only run the next shell command needed for the next decision. Avoid eager status

**Never use:** `git commit` or `git checkout -b` — always use `stackit create`.

## Prompt Note

After a successful create, record what was done by running:
```bash
command stackit notes add \
--prompt "<the user's original instruction that led to these changes>" \
--summary "<brief summary of what was actually done>" \
--model "<your model name, e.g. claude-sonnet-4>"
```
This captures LLM context on the commit for audit and memory purposes. Keep the prompt and summary concise (1-2 sentences each).

## Follow-up

After successful creation, use `AskUserQuestion`:
Expand Down
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ Modify the current branch by amending its commit or creating a new commit. Autom

**Never use:** `git commit --amend` — always use `stackit modify` so descendants are restacked.

## Prompt Note

After a successful modify, record what was done by running:
```bash
command stackit notes add \
--prompt "<the user's original instruction that led to these changes>" \
--summary "<brief summary of what was actually done>" \
--model "<your model name, e.g. claude-sonnet-4>"
```
This captures LLM context on the commit for audit and memory purposes. Keep the prompt and summary concise (1-2 sentences each).

## Follow-up

After successful modification, use `AskUserQuestion`:
Expand Down
Loading
Loading

AltStyle によって変換されたページ (->オリジナル) /