Add an architecture edge ratchet and waiting-state tokens #167
@@ -0,0 +1,36 @@
|
||||
# feature/architecture-baseline 任务卡
|
||||
|
||||
任务主记录:<PR 链接,task-finish 时填写>;状态:进行中;owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 5266d81c0d18500cc77d94b346d6ec1b424df260
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-architecture-baseline(worktree);分支:feature/architecture-baseline
|
||||
- 授权与交付终点:在 Dev 增加分层棘轮,在 ui 增加确认等待、差异和任务色令牌,在 frontend 与 ui 增加 knip 死导出棘轮,并改 DESIGN.md 与两份 AGENTS.md。允许 frontend、ui、Dev 各建 `feature/architecture-baseline`、推送、建 PR、合入。终点是这些 PR 已合入,然后在本 worktree 运行 `make task-finish`。未授权:改三个库、改 Core、发版、改写历史、变更分支保护。
|
||||
- 启动自检(2026-10-03T15:57Z):`make precheck` verdict READY。head=5266d81c0d,origin/main 同步,树干净,linked worktrees=0。必读 core/baseline.md、workflows/delivery.md §1、DESIGN.md 已读。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
- 目标:backend internal 与前端目录的依赖边只许减少或保持;新增边失败,消失的边告警。frontend 与 ui 的未使用导出数量不得增加。等你确认、差异增删、空闲与定时任务各有令牌。提示气泡、快捷键和文本膨胀写进 DESIGN.md。异步状态只有一个所有者。
|
||||
- 受影响:Dev 的 policy、scripts、Makefile、DESIGN.md、AGENTS.md;ui 的 tokens.css;frontend 的 AGENTS.md。
|
||||
- 非目标:不改对话运行态、权限、上下文压缩;不改三个库。
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | 5266d81c0d18500cc77d94b346d6ec1b424df260 | 5266d81c0d18500cc77d94b346d6ec1b424df260 | 2026-10-03T15:57:52Z |
|
||||
| 同步前验收 | 5266d81c0d18500cc77d94b346d6ec1b424df260 | 937152a563cc0513d68f6d6a27da3ce89d786c01 | 2026-10-03T16:03:04Z |
|
||||
|
||||
## 结果与验证
|
||||
|
||||
- 结果:ui `9c0345750600bd14f1b3144bb37819df97830fc1`。frontend `d5f59f1811268d099a6c354a29542809e640d4fe`。Dev 钉住这两枚指针,并加入分层棘轮。
|
||||
- 验证:
|
||||
- `make precheck`:verdict READY。`[repo] branch=main head=5266d81c0d`,`[remote] origin/main=5266d81c0d ahead=0 behind=0`,`[tree] clean`,`[worktrees] linked=0`,`[R0] 5266d81c0d18500cc77d94b346d6ec1b424df260`。
|
||||
- `make task-sync`(验收):`Already up to date.` `[synced] sync_base=5266d81c0d tested_head=937152a563 checked_at=2026-10-03T16:03:05Z`。
|
||||
- `bash scripts/lint-boundaries.sh`:architecture ok(67 edges),注入边失败,boundaries ok。
|
||||
- ui 与 frontend 的 design-guard 通过。knip 未使用导出 frontend 43、ui 55,不超过基线。
|
||||
- `make test` 退出码 0。`make build` 退出码 0(ui=9c03457 frontend=d5f59f1)。
|
||||
- 影响与剩余项:ui 指针从当时 Dev 钉住的 b4a4fc1 进到 ui 的 origin/main 再加本批令牌。不改三个库。合入后在本 worktree 运行 `make task-finish`。
|
||||
| 同步前验收 | 5266d81c0d18500cc77d94b346d6ec1b424df260 | e6503312eeb4c72a1e7b65ee6d7243dd12c1f4de | 2026-10-03T16:04:11Z |
|
||||
| 同步前验收 | 5266d81c0d18500cc77d94b346d6ec1b424df260 | 249ecb67eb02b996196577449847ff10724ace38 | 2026-10-03T16:04:26Z |
|
||||
@@ -11,6 +11,7 @@
|
||||
`@ui/motion.css` 的 `mk-*` 工具类;折叠一律用 `@ui` Collapsible 原语,禁止 `<details>`。
|
||||
- 守护测试(UI 仓与 Frontend 仓各一份 `design-guard.test.ts`)以棘轮拦截新增裸色值、
|
||||
私有 `@keyframes` 与行内字号;改风格 = `DESIGN.md` + 令牌文件同 PR 成对修改。
|
||||
- 异步状态只有一个所有者。事件按 seq 处理,过期事件丢弃。
|
||||
|
||||
## 任务启动门禁(强制)
|
||||
|
||||
|
||||
@@ -54,7 +54,11 @@ Agent-System-Dev 是桌面优先、Web 兼容的 AI 工作台。界面应**冷
|
||||
- 主按钮:单色系反色——`--primary`(深 `#ffffff` / 浅 `#0d0d0d`)配 `--primary-foreground`
|
||||
(反向黑/白)。品牌色保持克制。
|
||||
- 语义:`--ok #46bf72`、`--warn #ff8a30`、`--err #ff5c5c`(浅色 `#1e8a3e/#e07b00/#e03131`)。
|
||||
语义色只表语义;确认等待类统一绿色语言,不用红/橙装饰。
|
||||
语义色只表语义。
|
||||
- 等你确认用 `--wait`(深 `#8b93ff` / 浅 `#3a46c8` / 墨绿 `#3d5a9a`)和 `--wait-soft`。
|
||||
不借用成功绿,也不用红或橙装饰等待。
|
||||
- 差异视图:增加用 `--diff-add` / `--diff-add-text`,删除用 `--diff-remove` / `--diff-remove-text`。
|
||||
- 空闲任务用 `--task-idle`,定时任务用 `--task-scheduled`。两者不要混成同一种灰。
|
||||
|
||||
### 2.5 墨绿主题(可选,`data-theme='molv'`)
|
||||
|
||||
@@ -83,6 +87,7 @@ Agent-System-Dev 是桌面优先、Web 兼容的 AI 工作台。界面应**冷
|
||||
| `--fs-ui-caption` | −1px | 13 | 紧凑辅助文案 |
|
||||
| `--fs-ui-sm` | −2px | 12 | 次要信息、行内代码 |
|
||||
| `--fs-ui-xs` | −4px | 10 | 徽标、快捷键、极弱元数据 |
|
||||
| `--fs-ui-tooltip` | 同 `--fs-ui-sm` | 12 | 提示气泡。不另起字号 |
|
||||
|
||||
规则:UI 组件一律用标尺令牌;禁止行内 `font-size` 数值与 Tailwind 式任意字号
|
||||
(守护测试棘轮拦截)。用户调字号只改 `--ui-font-size`,不改根字号;图标、间距、圆角不随字号缩放。
|
||||
@@ -208,7 +213,15 @@ Primary(`--primary` 反色)、Ghost(透明,hover 出表面)、Danger
|
||||
- 普通用户界面只暴露任务与对话;插件/代理/治理类能力归系统管理区(管理员可见)。
|
||||
- 右栏收起时功能零缺失(D31:弹层不承载唯一步骤,每个操作另有对话内路径)。
|
||||
- 中文文案:动宾结构、不带句点结尾;快捷键用 `⌘`/kbd 标注。
|
||||
- 布局容忍长文本与翻译膨胀,不依赖截断作为唯一生存手段。
|
||||
- 布局按 `--text-expansion`(1.3)给按钮和标签留膨胀空间。不要把宽度定死成当前中文的字数。截断不是唯一生存手段。
|
||||
|
||||
### 10.1 快捷键
|
||||
|
||||
| 动作 | 按键 |
|
||||
|---|---|
|
||||
| 新建任务 | ⌘N / Ctrl+N |
|
||||
| 折叠或展开左栏 | ⌘B / Ctrl+B |
|
||||
| 打开第 n 个工作区 | ⌘1–9 / Ctrl+1–9 |
|
||||
|
||||
## 11. 禁止清单(守护测试覆盖项)
|
||||
|
||||
|
||||
@@ -88,10 +88,10 @@ eval-deliverables:
|
||||
cd $(BACKEND) && go test ./eval/ ./internal/quality/ -count=1
|
||||
|
||||
test-frontend: contracts
|
||||
cd $(FRONTEND) && npm ci --no-fund --no-audit && npx vitest run
|
||||
cd $(FRONTEND) && npm ci --no-fund --no-audit && npx vitest run && node scripts/check-knip.mjs
|
||||
|
||||
test-ui: contracts
|
||||
cd $(UI) && npm ci --no-fund --no-audit && npx tsc --noEmit && npx vitest run
|
||||
cd $(UI) && npm ci --no-fund --no-audit && npx tsc --noEmit && npx vitest run && node scripts/check-knip.mjs
|
||||
|
||||
test-agentcmd:
|
||||
cd deps/agent-command && go vet ./... && go test -count=1 ./...
|
||||
|
||||
+1
-1
Submodule modules/frontend updated: 8be98b4cbe...d5f59f1811
+1
-1
Submodule modules/ui updated: b4a4fc1b5f...9c03457506
@@ -0,0 +1,69 @@
|
||||
[
|
||||
"backend/app backend/bus",
|
||||
"backend/app backend/cardsvc",
|
||||
"backend/app backend/cmdbus",
|
||||
"backend/app backend/hostapi",
|
||||
"backend/app backend/intentgate",
|
||||
"backend/app backend/pgbus",
|
||||
"backend/app backend/plugins",
|
||||
"backend/app backend/profiles",
|
||||
"backend/app backend/prompts",
|
||||
"backend/app backend/rag",
|
||||
"backend/app backend/ratelimit",
|
||||
"backend/app backend/runmgr",
|
||||
"backend/app backend/runtime",
|
||||
"backend/app backend/server",
|
||||
"backend/app backend/settings",
|
||||
"backend/app backend/skills",
|
||||
"backend/app backend/squadadapter",
|
||||
"backend/app backend/store",
|
||||
"backend/app backend/tools",
|
||||
"backend/app backend/workflow",
|
||||
"backend/cardsvc backend/store",
|
||||
"backend/hostapi backend/store",
|
||||
"backend/pgbus backend/bus",
|
||||
"backend/pgbus backend/store",
|
||||
"backend/plugins backend/settings",
|
||||
"backend/plugins backend/store",
|
||||
"backend/plugins backend/tools",
|
||||
"backend/plugins backend/workflow",
|
||||
"backend/profiles backend/store",
|
||||
"backend/prompts backend/store",
|
||||
"backend/rag backend/settings",
|
||||
"backend/rag backend/store",
|
||||
"backend/runmgr backend/bus",
|
||||
"backend/runmgr backend/cardsvc",
|
||||
"backend/runmgr backend/ingest",
|
||||
"backend/runmgr backend/profiles",
|
||||
"backend/runmgr backend/runtime",
|
||||
"backend/runmgr backend/settings",
|
||||
"backend/runmgr backend/store",
|
||||
"backend/runmgr backend/tools",
|
||||
"backend/runmgr backend/workflow",
|
||||
"backend/runtime backend/ctxasm",
|
||||
"backend/runtime backend/docgen",
|
||||
"backend/runtime backend/prompts",
|
||||
"backend/runtime backend/quality",
|
||||
"backend/runtime backend/squadadapter",
|
||||
"backend/runtime backend/tools",
|
||||
"backend/runtime backend/workflow",
|
||||
"backend/server backend/cardsvc",
|
||||
"backend/server backend/cmdbus",
|
||||
"backend/server backend/ingest",
|
||||
"backend/server backend/intentgate",
|
||||
"backend/server backend/plugins",
|
||||
"backend/server backend/profiles",
|
||||
"backend/server backend/prompts",
|
||||
"backend/server backend/ratelimit",
|
||||
"backend/server backend/runmgr",
|
||||
"backend/server backend/settings",
|
||||
"backend/server backend/store",
|
||||
"backend/server backend/tools",
|
||||
"backend/server backend/workflow",
|
||||
"backend/settings backend/store",
|
||||
"backend/squadadapter backend/quality",
|
||||
"backend/store backend/blobstore",
|
||||
"backend/tools backend/quality",
|
||||
"frontend/app frontend/components",
|
||||
"frontend/components frontend/app"
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
# 分层棘轮。新增跨组依赖边失败,消失的边只告警。
|
||||
# 基线见 architecture-baseline.json。改分层先改本文件,再更新基线。
|
||||
backend:
|
||||
root: modules/backend/internal
|
||||
import_prefix: agent-system-backend/internal/
|
||||
frontend:
|
||||
root: modules/frontend/src
|
||||
groups:
|
||||
- name: app
|
||||
dir: .
|
||||
- name: components
|
||||
dir: components
|
||||
@@ -0,0 +1,270 @@
|
||||
// 分层依赖棘轮:backend internal 包与前端目录组之间的导入边。
|
||||
// 相对基线多出来的边失败;基线里已经消失的边只告警。
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
root := flag.String("root", ".", "repository root")
|
||||
baseline := flag.String("baseline", "policy/architecture-baseline.json", "recorded edges")
|
||||
write := flag.Bool("write", false, "rewrite the baseline from the current tree")
|
||||
inject := flag.String("inject", "", "extra edge from->to, used to prove a new edge fails")
|
||||
flag.Parse()
|
||||
|
||||
if err := checkPolicy(*root); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "architecture: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
edges, err := collect(*root)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "architecture: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if *inject != "" {
|
||||
parts := strings.Split(*inject, "->")
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
fmt.Fprintln(os.Stderr, "architecture: -inject must be from->to")
|
||||
os.Exit(2)
|
||||
}
|
||||
edges = append(edges, parts[0]+" "+parts[1])
|
||||
}
|
||||
sort.Strings(edges)
|
||||
edges = unique(edges)
|
||||
if *write {
|
||||
raw, err := json.MarshalIndent(edges, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "architecture: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
raw = append(raw, '\n')
|
||||
if err := os.WriteFile(*baseline, raw, 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "architecture: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("wrote %d edges\n", len(edges))
|
||||
return
|
||||
}
|
||||
recorded, err := readBaseline(*baseline)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "architecture: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
observed := map[string]bool{}
|
||||
for _, e := range edges {
|
||||
observed[e] = true
|
||||
}
|
||||
var fresh, stale []string
|
||||
for _, e := range edges {
|
||||
if !recorded[e] {
|
||||
fresh = append(fresh, e)
|
||||
}
|
||||
}
|
||||
for e := range recorded {
|
||||
if !observed[e] {
|
||||
stale = append(stale, e)
|
||||
}
|
||||
}
|
||||
sort.Strings(stale)
|
||||
if len(stale) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "warn: architecture edges no longer present; delete them from the baseline")
|
||||
for _, e := range stale {
|
||||
fmt.Fprintln(os.Stderr, " ", e)
|
||||
}
|
||||
}
|
||||
if len(fresh) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "architecture: new dependency edges")
|
||||
for _, e := range fresh {
|
||||
fmt.Fprintln(os.Stderr, " ", e)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("architecture ok (%d edges)\n", len(edges))
|
||||
}
|
||||
|
||||
func readBaseline(path string) (map[string]bool, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var list []string
|
||||
if err := json.Unmarshal(raw, &list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := map[string]bool{}
|
||||
for _, e := range list {
|
||||
out[e] = true
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func checkPolicy(root string) error {
|
||||
raw, err := os.ReadFile(filepath.Join(root, "policy/architecture.yaml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
text := string(raw)
|
||||
for _, need := range []string{"modules/backend/internal", "modules/frontend/src", "components"} {
|
||||
if !strings.Contains(text, need) {
|
||||
return fmt.Errorf("policy/architecture.yaml missing %s", need)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func collect(root string) ([]string, error) {
|
||||
var edges []string
|
||||
be, err := backendEdges(filepath.Join(root, "modules/backend/internal"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
edges = append(edges, be...)
|
||||
fe, err := frontendEdges(filepath.Join(root, "modules/frontend/src"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
edges = append(edges, fe...)
|
||||
return edges, nil
|
||||
}
|
||||
|
||||
func backendEdges(dir string) ([]string, error) {
|
||||
var edges []string
|
||||
err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
||||
return nil
|
||||
}
|
||||
from := backendNode(dir, path)
|
||||
if from == "" {
|
||||
return nil
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, imp := range f.Imports {
|
||||
p := strings.Trim(imp.Path.Value, `"`)
|
||||
const prefix = "agent-system-backend/internal/"
|
||||
if !strings.HasPrefix(p, prefix) {
|
||||
continue
|
||||
}
|
||||
rest := strings.TrimPrefix(p, prefix)
|
||||
seg := strings.Split(rest, "/")[0]
|
||||
to := "backend/" + seg
|
||||
if to == from {
|
||||
continue
|
||||
}
|
||||
edges = append(edges, from+" "+to)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return edges, err
|
||||
}
|
||||
|
||||
func backendNode(root, path string) string {
|
||||
rel, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
seg := strings.Split(filepath.ToSlash(rel), "/")[0]
|
||||
if seg == "" || seg == "." {
|
||||
return ""
|
||||
}
|
||||
return "backend/" + seg
|
||||
}
|
||||
|
||||
func frontendEdges(src string) ([]string, error) {
|
||||
var edges []string
|
||||
err := filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(path, ".ts") && !strings.HasSuffix(path, ".tsx") {
|
||||
return nil
|
||||
}
|
||||
if strings.HasSuffix(path, ".d.ts") {
|
||||
return nil
|
||||
}
|
||||
from := frontendNode(src, path)
|
||||
text, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, spec := range importSpecs(text) {
|
||||
if !strings.HasPrefix(spec, ".") {
|
||||
continue
|
||||
}
|
||||
target := filepath.Clean(filepath.Join(filepath.Dir(path), spec))
|
||||
if !strings.HasPrefix(target, src) {
|
||||
continue
|
||||
}
|
||||
to := frontendNode(src, target)
|
||||
if to == "" || to == from {
|
||||
continue
|
||||
}
|
||||
edges = append(edges, from+" "+to)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return edges, err
|
||||
}
|
||||
|
||||
func frontendNode(src, path string) string {
|
||||
rel, err := filepath.Rel(src, path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
rel = filepath.ToSlash(rel)
|
||||
if strings.HasPrefix(rel, "components/") || rel == "components" {
|
||||
return "frontend/components"
|
||||
}
|
||||
return "frontend/app"
|
||||
}
|
||||
|
||||
func importSpecs(src []byte) []string {
|
||||
var specs []string
|
||||
sc := bufio.NewScanner(bytes.NewReader(src))
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
for _, marker := range []string{"from '", `from "`, "import('", `import("`} {
|
||||
i := strings.Index(line, marker)
|
||||
if i < 0 {
|
||||
continue
|
||||
}
|
||||
rest := line[i+len(marker):]
|
||||
quote := marker[len(marker)-1]
|
||||
end := strings.IndexByte(rest, quote)
|
||||
if end > 0 {
|
||||
specs = append(specs, rest[:end])
|
||||
}
|
||||
}
|
||||
}
|
||||
return specs
|
||||
}
|
||||
|
||||
func unique(in []string) []string {
|
||||
if len(in) == 0 {
|
||||
return in
|
||||
}
|
||||
out := in[:1]
|
||||
for _, s := range in[1:] {
|
||||
if s != out[len(out)-1] {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# 分层棘轮:新增依赖边失败,消失的边只告警。
|
||||
# 负例:注入一条基线里没有的边,必须失败。
|
||||
set -euo pipefail
|
||||
root=$(cd "$(dirname "$0")/.." && pwd)
|
||||
base="$root/policy/architecture-baseline.json"
|
||||
tool=(go run "$root/scripts/lint-architecture.go" -root "$root" -baseline "$base")
|
||||
|
||||
( cd "$root/scripts" && "${tool[@]}" )
|
||||
|
||||
set +e
|
||||
( cd "$root/scripts" && "${tool[@]}" -inject 'backend/store->backend/__architecture_probe__' ) >/tmp/architecture-probe.out 2>&1
|
||||
code=$?
|
||||
set -e
|
||||
if [ "$code" -eq 0 ]; then
|
||||
echo "architecture: injected edge was accepted" >&2
|
||||
cat /tmp/architecture-probe.out >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q '__architecture_probe__' /tmp/architecture-probe.out; then
|
||||
echo "architecture: injected edge did not fail for the expected reason" >&2
|
||||
cat /tmp/architecture-probe.out >&2
|
||||
exit 1
|
||||
fi
|
||||
rm -f /tmp/architecture-probe.out
|
||||
echo "architecture negative ok"
|
||||
@@ -96,4 +96,5 @@ done < <(find "$root/deps/agent-native" "$root/deps/agent-squad" "$root/modules/
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
bash "$root/scripts/lint-architecture.sh"
|
||||
echo "boundaries ok"
|
||||
|
||||
Reference in New Issue
Block a user