chore: snapshot WIP previo + flow 0008 + 7 sub-issues (0112-0119)
Snapshot de WIP acumulado de sesiones previas antes de merge wave 1 del flow 0008 (kanban_cpp + agent_runner_api + DoD schema). Incluye: - dev/flows/0008-kanban-cpp-and-agent-workflows.md - dev/issues/0112-0119*.md (7 sub-issues) - WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
-- 014: service: bloque del frontmatter de app.md → columnas apps + tabla service_targets
|
||||
-- Issue 0105
|
||||
|
||||
ALTER TABLE apps ADD COLUMN service_port INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE apps ADD COLUMN service_health_endpoint TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE apps ADD COLUMN service_health_timeout_s INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE apps ADD COLUMN service_systemd_unit TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE apps ADD COLUMN service_systemd_scope TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE apps ADD COLUMN service_restart_policy TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE apps ADD COLUMN service_runtime TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE apps ADD COLUMN service_is_local_only INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS service_targets (
|
||||
app_id TEXT NOT NULL,
|
||||
pc_id TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'primary',
|
||||
PRIMARY KEY (app_id, pc_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_service_targets_pc ON service_targets(pc_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_service_targets_app ON service_targets(app_id);
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 015: semver per-app
|
||||
-- Cada app declara `version: X.Y.Z` en su app.md (default 0.1.0).
|
||||
-- Bumped por /version cuando /fix-issue (u otro flujo) toca codigo de la app.
|
||||
-- Trazabilidad: ## Capability growth log dentro del propio app.md.
|
||||
|
||||
ALTER TABLE apps ADD COLUMN version TEXT NOT NULL DEFAULT '0.1.0';
|
||||
+35
-19
@@ -105,25 +105,41 @@ type Type struct {
|
||||
|
||||
// App represents an entry in the apps table.
|
||||
type App struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Lang string `json:"lang"`
|
||||
Domain string `json:"domain"`
|
||||
Description string `json:"description"`
|
||||
Tags []string `json:"tags"`
|
||||
UsesFunctions []string `json:"uses_functions"`
|
||||
UsesTypes []string `json:"uses_types"`
|
||||
UsesModules []string `json:"uses_modules"`
|
||||
Framework string `json:"framework"`
|
||||
EntryPoint string `json:"entry_point"`
|
||||
Documentation string `json:"documentation"`
|
||||
Notes string `json:"notes"`
|
||||
DirPath string `json:"dir_path"`
|
||||
ContentHash string `json:"content_hash"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
ProjectID string `json:"project_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Lang string `json:"lang"`
|
||||
Domain string `json:"domain"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Tags []string `json:"tags"`
|
||||
UsesFunctions []string `json:"uses_functions"`
|
||||
UsesTypes []string `json:"uses_types"`
|
||||
UsesModules []string `json:"uses_modules"`
|
||||
Framework string `json:"framework"`
|
||||
EntryPoint string `json:"entry_point"`
|
||||
Documentation string `json:"documentation"`
|
||||
Notes string `json:"notes"`
|
||||
DirPath string `json:"dir_path"`
|
||||
ContentHash string `json:"content_hash"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
ProjectID string `json:"project_id"`
|
||||
Service *ServiceSpec `json:"service,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ServiceSpec describes how an app runs as a long-lived service.
|
||||
// Populated from the `service:` block of app.md frontmatter (issue 0105).
|
||||
type ServiceSpec struct {
|
||||
Port int `json:"port,omitempty"`
|
||||
HealthEndpoint string `json:"health_endpoint,omitempty"`
|
||||
HealthTimeoutS int `json:"health_timeout_s,omitempty"`
|
||||
SystemdUnit string `json:"systemd_unit,omitempty"`
|
||||
SystemdScope string `json:"systemd_scope,omitempty"`
|
||||
RestartPolicy string `json:"restart_policy,omitempty"`
|
||||
Runtime string `json:"runtime,omitempty"`
|
||||
IsLocalOnly bool `json:"is_local_only,omitempty"`
|
||||
PCTargets []string `json:"pc_targets,omitempty"`
|
||||
}
|
||||
|
||||
// Analysis represents an entry in the analysis table.
|
||||
|
||||
+49
-12
@@ -75,18 +75,33 @@ type rawType struct {
|
||||
|
||||
// rawApp mirrors the YAML frontmatter of an app .md file.
|
||||
type rawApp struct {
|
||||
Name string `yaml:"name"`
|
||||
Lang string `yaml:"lang"`
|
||||
Domain string `yaml:"domain"`
|
||||
Description string `yaml:"description"`
|
||||
Tags []string `yaml:"tags"`
|
||||
UsesFunctions []string `yaml:"uses_functions"`
|
||||
UsesTypes []string `yaml:"uses_types"`
|
||||
UsesModules []string `yaml:"uses_modules"`
|
||||
Framework string `yaml:"framework"`
|
||||
EntryPoint string `yaml:"entry_point"`
|
||||
DirPath string `yaml:"dir_path"`
|
||||
RepoURL string `yaml:"repo_url"`
|
||||
Name string `yaml:"name"`
|
||||
Lang string `yaml:"lang"`
|
||||
Domain string `yaml:"domain"`
|
||||
Version string `yaml:"version"`
|
||||
Description string `yaml:"description"`
|
||||
Tags []string `yaml:"tags"`
|
||||
UsesFunctions []string `yaml:"uses_functions"`
|
||||
UsesTypes []string `yaml:"uses_types"`
|
||||
UsesModules []string `yaml:"uses_modules"`
|
||||
Framework string `yaml:"framework"`
|
||||
EntryPoint string `yaml:"entry_point"`
|
||||
DirPath string `yaml:"dir_path"`
|
||||
RepoURL string `yaml:"repo_url"`
|
||||
Service *rawService `yaml:"service"`
|
||||
}
|
||||
|
||||
// rawService mirrors the `service:` block of an app.md frontmatter (issue 0105).
|
||||
type rawService struct {
|
||||
Port *int `yaml:"port"`
|
||||
HealthEndpoint string `yaml:"health_endpoint"`
|
||||
HealthTimeoutS int `yaml:"health_timeout_s"`
|
||||
SystemdUnit string `yaml:"systemd_unit"`
|
||||
SystemdScope string `yaml:"systemd_scope"`
|
||||
RestartPolicy string `yaml:"restart_policy"`
|
||||
Runtime string `yaml:"runtime"`
|
||||
IsLocalOnly bool `yaml:"is_local_only"`
|
||||
PCTargets []string `yaml:"pc_targets"`
|
||||
}
|
||||
|
||||
// rawAnalysis mirrors the YAML frontmatter of an analysis .md file.
|
||||
@@ -325,11 +340,16 @@ func ParseAppMD(path string, root string) (*App, error) {
|
||||
|
||||
sections := extractSections(body)
|
||||
|
||||
if raw.Version == "" {
|
||||
raw.Version = "0.1.0"
|
||||
}
|
||||
|
||||
a := &App{
|
||||
ID: GenerateID(raw.Name, raw.Lang, raw.Domain),
|
||||
Name: raw.Name,
|
||||
Lang: raw.Lang,
|
||||
Domain: raw.Domain,
|
||||
Version: raw.Version,
|
||||
Description: raw.Description,
|
||||
Tags: raw.Tags,
|
||||
UsesFunctions: raw.UsesFunctions,
|
||||
@@ -343,6 +363,23 @@ func ParseAppMD(path string, root string) (*App, error) {
|
||||
RepoURL: raw.RepoURL,
|
||||
}
|
||||
|
||||
if raw.Service != nil {
|
||||
spec := &ServiceSpec{
|
||||
HealthEndpoint: raw.Service.HealthEndpoint,
|
||||
HealthTimeoutS: raw.Service.HealthTimeoutS,
|
||||
SystemdUnit: raw.Service.SystemdUnit,
|
||||
SystemdScope: raw.Service.SystemdScope,
|
||||
RestartPolicy: raw.Service.RestartPolicy,
|
||||
Runtime: raw.Service.Runtime,
|
||||
IsLocalOnly: raw.Service.IsLocalOnly,
|
||||
PCTargets: raw.Service.PCTargets,
|
||||
}
|
||||
if raw.Service.Port != nil {
|
||||
spec.Port = *raw.Service.Port
|
||||
}
|
||||
a.Service = spec
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
|
||||
+105
-3
@@ -303,18 +303,70 @@ func (db *DB) InsertApp(a *App) error {
|
||||
a.ID = GenerateID(a.Name, a.Lang, a.Domain)
|
||||
}
|
||||
|
||||
var (
|
||||
svcPort int
|
||||
svcHealth string
|
||||
svcHealthTO int
|
||||
svcUnit string
|
||||
svcScope string
|
||||
svcRestart string
|
||||
svcRuntime string
|
||||
svcLocalOnly int
|
||||
)
|
||||
if a.Service != nil {
|
||||
svcPort = a.Service.Port
|
||||
svcHealth = a.Service.HealthEndpoint
|
||||
svcHealthTO = a.Service.HealthTimeoutS
|
||||
svcUnit = a.Service.SystemdUnit
|
||||
svcScope = a.Service.SystemdScope
|
||||
svcRestart = a.Service.RestartPolicy
|
||||
svcRuntime = a.Service.Runtime
|
||||
if a.Service.IsLocalOnly {
|
||||
svcLocalOnly = 1
|
||||
}
|
||||
}
|
||||
|
||||
if a.Version == "" {
|
||||
a.Version = "0.1.0"
|
||||
}
|
||||
|
||||
_, err := db.conn.Exec(`
|
||||
INSERT OR REPLACE INTO apps (
|
||||
id, name, lang, domain, description, tags,
|
||||
uses_functions, uses_types, framework, entry_point,
|
||||
documentation, notes, dir_path, content_hash, created_at, updated_at, repo_url, project_id, uses_modules
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
documentation, notes, dir_path, content_hash, created_at, updated_at, repo_url, project_id, uses_modules,
|
||||
service_port, service_health_endpoint, service_health_timeout_s,
|
||||
service_systemd_unit, service_systemd_scope, service_restart_policy,
|
||||
service_runtime, service_is_local_only, version
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
a.ID, a.Name, a.Lang, a.Domain, a.Description, marshalStrings(a.Tags),
|
||||
marshalStrings(a.UsesFunctions), marshalStrings(a.UsesTypes), a.Framework, a.EntryPoint,
|
||||
a.Documentation, a.Notes, a.DirPath, a.ContentHash, a.CreatedAt.Format(time.RFC3339), a.UpdatedAt.Format(time.RFC3339),
|
||||
a.RepoURL, a.ProjectID, marshalStrings(a.UsesModules),
|
||||
svcPort, svcHealth, svcHealthTO, svcUnit, svcScope, svcRestart, svcRuntime, svcLocalOnly, a.Version,
|
||||
)
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace service_targets for this app (idempotent).
|
||||
if _, err := db.conn.Exec("DELETE FROM service_targets WHERE app_id = ?", a.ID); err != nil {
|
||||
return fmt.Errorf("clearing service_targets for %s: %w", a.ID, err)
|
||||
}
|
||||
if a.Service != nil {
|
||||
for _, pc := range a.Service.PCTargets {
|
||||
if pc == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := db.conn.Exec(
|
||||
"INSERT OR REPLACE INTO service_targets (app_id, pc_id, role) VALUES (?, ?, 'primary')",
|
||||
a.ID, pc,
|
||||
); err != nil {
|
||||
return fmt.Errorf("inserting service_target %s/%s: %w", a.ID, pc, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetApp returns a single app by ID.
|
||||
@@ -374,12 +426,16 @@ func scanApps(rows interface{ Next() bool; Scan(...any) error }) ([]App, error)
|
||||
var a App
|
||||
var tagsJSON, usesFnJSON, usesTypJSON, usesModJSON string
|
||||
var createdAt, updatedAt string
|
||||
var svcPort, svcHealthTO, svcLocalOnly int
|
||||
var svcHealth, svcUnit, svcScope, svcRestart, svcRuntime string
|
||||
|
||||
err := rows.Scan(
|
||||
&a.ID, &a.Name, &a.Lang, &a.Domain, &a.Description, &tagsJSON,
|
||||
&usesFnJSON, &usesTypJSON, &a.Framework, &a.EntryPoint,
|
||||
&a.Documentation, &a.Notes, &a.DirPath, &createdAt, &updatedAt, &a.ContentHash,
|
||||
&a.RepoURL, &a.ProjectID, &usesModJSON,
|
||||
&svcPort, &svcHealth, &svcHealthTO, &svcUnit, &svcScope, &svcRestart, &svcRuntime, &svcLocalOnly,
|
||||
&a.Version,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scanning app: %w", err)
|
||||
@@ -392,11 +448,47 @@ func scanApps(rows interface{ Next() bool; Scan(...any) error }) ([]App, error)
|
||||
a.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
||||
a.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
|
||||
|
||||
if svcPort != 0 || svcHealth != "" || svcUnit != "" || svcScope != "" || svcRestart != "" || svcRuntime != "" || svcLocalOnly != 0 {
|
||||
a.Service = &ServiceSpec{
|
||||
Port: svcPort,
|
||||
HealthEndpoint: svcHealth,
|
||||
HealthTimeoutS: svcHealthTO,
|
||||
SystemdUnit: svcUnit,
|
||||
SystemdScope: svcScope,
|
||||
RestartPolicy: svcRestart,
|
||||
Runtime: svcRuntime,
|
||||
IsLocalOnly: svcLocalOnly != 0,
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, a)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetServicePCTargets returns the pc_ids declared in service_targets for an app.
|
||||
// Empty slice when the app has no declared targets. Issue 0105.
|
||||
func (db *DB) GetServicePCTargets(appID string) ([]string, error) {
|
||||
rows, err := db.conn.Query(
|
||||
"SELECT pc_id FROM service_targets WHERE app_id = ? ORDER BY pc_id",
|
||||
appID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []string
|
||||
for rows.Next() {
|
||||
var pc string
|
||||
if err := rows.Scan(&pc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, pc)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Purge deletes all data from functions, types, apps, analysis, projects, vaults and modules. Used before re-indexing.
|
||||
func (db *DB) Purge() error {
|
||||
if _, err := db.conn.Exec("DELETE FROM functions"); err != nil {
|
||||
@@ -408,6 +500,9 @@ func (db *DB) Purge() error {
|
||||
if _, err := db.conn.Exec("DELETE FROM apps"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.conn.Exec("DELETE FROM service_targets"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.conn.Exec("DELETE FROM analysis"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -435,11 +530,18 @@ func (db *DB) PurgeLocalOnly(localAppIDs, localAnalysisIDs, localProjectIDs map[
|
||||
if _, err := db.conn.Exec("DELETE FROM apps WHERE id = ?", id); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.conn.Exec("DELETE FROM service_targets WHERE app_id = ?", id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Delete apps without repo_url (legacy local-only apps not yet pushed)
|
||||
if _, err := db.conn.Exec("DELETE FROM apps WHERE repo_url = '' OR repo_url IS NULL"); err != nil {
|
||||
return err
|
||||
}
|
||||
// Orphan service_targets cleanup
|
||||
if _, err := db.conn.Exec("DELETE FROM service_targets WHERE app_id NOT IN (SELECT id FROM apps)"); err != nil {
|
||||
return err
|
||||
}
|
||||
// Same for analysis
|
||||
for id := range localAnalysisIDs {
|
||||
if _, err := db.conn.Exec("DELETE FROM analysis WHERE id = ?", id); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user