本文へ移動
cccskills
無料GitHub で公開

db-canonicalize-table

Bringing an existing table onto the platform base standard. Use when asked to canonicalize, retrofit, or 'Wave 3' a table, run apply_rls or an org backfill, retire a bespoke *_versions table, or move it onto platform comments/associations/permissions. NOT for a schema move (use db-move-table-schema).

インストール方法を見る

含まれるファイル(2)

  • SKILL.md25.8 KB
  • retire-versions-table.md5.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Canonicalize a table

🚨 Never remove, narrow, supersede or suppress platform_admin_read — it is our own admin database access (the admin dashboard reads every row through it). Check before and after any policy change: ../common-docs/policies/our-own-admin-database-access.md (the query must return 0).

Make <schema>.<table> (token <token>) fully conform to the platform standard. Read ../db-change/TOOLKIT.md (live signatures, gotchas) + ../db-change/SKILL.md first. Project: brsgrqvjdzwihsvnfqkf. Order matters — do the steps in sequence.

This is the ONE recipe for getting any table onto the base standard ("retrofit", "base retrofit", "Wave 3", "bring <X> onto the platform base entity"), preserving existing behavior exactly: take it all the way to certified, or stop at the transition-state floor (zero FAIL + only the legacy WARNs — see the certify bar below). Relocating a table → db-move-table-schema; retiring one → db-deprecate-table.

🛑 Before you write one line — obey db-change's PRIME DIRECTIVE (§top of ../db-change/SKILL.md)

The app is DOWN until fully canonical; there is no safe half-state. So:

  • Meet EVERY canonical requirement for this table in ONE pass across ALL layers (SQL → db/generate.py → Python usages → FE types → Next.js usages → both repos → commit → push). Never start a table you won't finish this pass. Never leave one layer repointed and another not — that broken middle is the whole disaster.
  • First, look at the real table AND its data (row counts — live or test?). Then write a SHORT bullet checklist of what canonical requires here. Not a report.
  • "Behavior preserved exactly" is NOT a license to skip a canonical requirement. If preserving today's behavior would keep a non-canonical structure, that is a conflict you RAISE, not resolve on your own.
  • 🔴 NO bespoke *_versions table survives, and you do NOT get to keep one silently. Canonical versioning = history.row_versions via the _history trigger + the generic version_* RPCs. A per-entity *_versions table (code_file_versions, note_versions, …) is legacy drift to RETIRE — migrate its rows into history.row_versions, repoint promote_version→version_restore + every reader, then deprecate it. BUT versioning is not right for every table — so if you think this table should keep its table, or shouldn't be versioned at all, that is a QUESTION you ask the human BEFORE starting (state your view + the fact, e.g. row count), get an explicit yes/no, and only then proceed. Leaving the old version table because you decided it was safer — with no ask — is the exact failure that has cost this project weeks. (Setting is_versioned=false to silence the gate while the bespoke table lives on is that same failure in disguise.)
  • Any other requirement you want to deviate from (skip a drop, keep an old column, defer a repoint) → same rule: ask up front, explicit yes/no, no clear answer = stop and ask again. Decisions are made BEFORE you start, then you execute 100%.

📼 Retiring a bespoke *_versions table

The one-pass recipe (backfill → capture → repoint the legacy version RPCs → deprecated), its gotchas, and the two live CRACKS in the canonical version system. Entity carries a per-entity *_versions table (or the Prime Directive versioning question above was answered "retire it") → read retire-versions-table.md before Step 5.

⚡ Field realities — read these first, they each cost a round-trip (verified 2026-07-05)

  1. This is where a DB-wide finding on an uncertified table should land. Under canonical-first triage, a problem that appears only on uncertified tables is fixed by converting the table here — never by patching the old shape.

  2. A table is usually FAR more canonical than "not started." A prior "canonicalize + move" migration often already added created_by/visibility/deleted_at + canonical RLS + _stamp_actor/_touch_row, and left only: the kill-list columns (user_id/is_public/is_deleted) undropped + the base FK constraints missing. Run iam.verify_canonical(s,t,tok) for EVERY involved table BEFORE writing anything — it prints the exact remaining gap. Don't assume; the delta is often tiny.

    • The gate keys on the TOKEN, never the table name — and the token is often singular while the table is plural (code_file ⟷ code_files, code_folder ⟷ code_file_folders). Passing the table name as the token yields a signature TRIO of FALSE fails — entity_registered ("no entity_types row for token=…"), policy_uses_has_access ("std_select does not call has_access('…')"), sharing_token ("registry resource_type=… != token=…"). If you see exactly those three, you passed the wrong token — get it from platform.entity_types/audit.summary, not by pluralizing the table name. (The admin verify UI now warns on this mismatch.)
  3. canonical_certify reads a CACHED snapshot — audit.refresh() is MANDATORY before it's truthful. After you CREATE OR REPLACE a broken dependent function or fix anything, canonical_certify_ok KEEPS reporting the old broken_dependent_fn/finding until you cd aidream && uv run python scripts/audit_refresh.py (session mode, never port 6543). A "still broken" you already fixed is the #1 false failure — refresh, then re-check.

  4. A broken dependent function blocks certify even when its breakage is unrelated to your table. (promote_version referenced a deprecated note_versions and blocked code_file certify.) Fixing it — e.g. neutralizing a dead branch that points at a retired table — is part of "done." Find them with audit.table_impact(s,t) (currently_broken=true).

  5. Components (is_component=true) still need organization_id + version + _touch_row + the base FKs. The gate FAILs base_organization_id/base_version/trg_touch_row/base_*_fk on components too. TWO traps: (a) the moment you ADD organization_id to a component, base_org_not_null flips SKIP→FAIL (column now exists, nullable) → SET NOT NULL after backfilling from the parent; (b) a soft_delete WARN blocks certify → add deleted_at + UPDATE entity_types SET has_soft_delete=true.

  6. is_versioned=true with no _history trigger is now a hard trg_version_capture FAIL. If the table versions via a bespoke *_versions table (or doesn't version at all), the honest, behavior-preserving fix is UPDATE platform.entity_types SET is_versioned=false — it makes the check SKIP without adding redundant double-capture. Only attach _history if you genuinely want it on history.row_versions.

  7. organization_id NOT NULL and every write names its organization — no trigger fills one. public._stamp_org_default no longer exists (verified live 2026-09-26) and must not come back: a database trigger choosing an organization is the substitution CLAUDE.md forbids (common-docs/systems/architecture/database/projects/no-db-assigned-org/PLAN.md). An org-forgetting write fails loudly with 23502 at the caller — that is correct.

  8. Service-role (Python) inserts MUST set created_by explicitly — _stamp_actor can't fill it without auth.uid()/app.user_id. A supabase-py service-client insert that dropped user_id must now pass created_by=<owner> and organization_id=<owning org> explicitly. (A JWT/RLS-authed FE write is stamped automatically — drop the owner column there.)

  9. Dropping a column/table is a clean cut, and finding MORE consumers than you expected is NORMAL — grind through them, NEVER revert. Before you drop, run the full Consumer discovery protocol (../db-change/SKILL.md §🔎): grep the name in EVERY format (snake · Pascal · camel · plural · manager-instance), open every hit including lazy/mid-file imports and generated hubs (package_db_wiring.py, auto_config*.py, db_requirements.py manifests, database.types.ts), and fix raw-SQL strings + getattr(row,"field",default) (the silent-column trap) — tsc/db/generate.py/dead-relations are the LAST-step backstop, not the search. When the blast radius turns out bigger than "this table is fairly new" implied (a second feature module, a whole router, a manifest), that is the migration working — repoint it, do not re-add the table (that panic-revert cost hours on skill.project/resource). The only stop is a genuine human decision (Prime Directive rule 4), flagged with the drop still in place.

Step 0 — Classify the variant and the level

The level first — platform.entity_types.data_class, by the law common-docs/policies/access-ladder.md: every table starts at organization (canonical examples: Agent, Sales deal); public only when it is meant to be seen outside the organization without being shared; confidential / private only with Arman's approval. A component carries no level of its own — it has its parent's. The variant below is the RLS shape, not the level.

  • entity — independent business object (its own owner/visibility). Most tables.
  • component — child whose access IS its parent's, full depth (versions, events, line-items). RLS defers to a composition parent; no own visibility.
  • ledger — append-only org-scoped log. No user writes, no version/soft-delete.
  • restricted — secret-bearing system tables (AI vendor config — ai.endpoint/ai.api/ai.offering). std_select = owner OR super-admin only — deliberately no iam.has_access path, because has_access would leak viewer access through the global-readable system org.
  • system — catalog/shared-definition tables. Requires a visibility column. std_select = public-visibility OR owner OR has_access. Check existing columns/policies/triggers first: \d-style via information_schema.columns, pg_policy, pg_trigger (see TOOLKIT.md §5–6).

Step 1 — Base columns (additive, idempotent)

public.* table → use the driver (adds org/created_by/updated_by/updated_at/version, backfills, attaches _touch_row+_stamp_actor):

select platform.retrofit_entity('<table>','<token>',
  '<personal|parent|keep>', '<owner_col>',           -- e.g. 'personal','user_id'
  '<parent_table_or_null>','<parent_fk_or_null>',     -- for 'parent' strategy
  '<legacy_updated_at_trigger_or_null>');             -- dropped so backfill doesn't bump updated_at

Ordering (verified): retrofit_entity backfills created_by + org before attaching the triggers, so its backfills don't churn version/updated_at. The visibility/deleted_at columns it does NOT add — so add visibility and run the is_public→visibility / is_deleted→deleted_at normalization before calling retrofit_entity (triggers not yet attached). Doing them after leaves freshly-migrated rows at version=2 with updated_at=now() (looks edited when it was only migrated). Schema-homed table (NOT public) → retrofit_entity does NOT work; hand-roll:

alter table <schema>.<table> add column if not exists organization_id uuid;
alter table <schema>.<table> add column if not exists created_by uuid;       -- if a non-uuid created_by exists, rename it to created_by_kind first
alter table <schema>.<table> add column if not exists updated_by uuid;
alter table <schema>.<table> add column if not exists updated_at timestamptz not null default now();
alter table <schema>.<table> add column if not exists version int not null default 1;
alter table <schema>.<table> add column if not exists deleted_at timestamptz;
alter table <schema>.<table> add column if not exists metadata jsonb not null default '{}';
alter table <schema>.<table> add column if not exists visibility platform.visibility not null default '<TIER>';  -- entity/shareable only
-- <TIER> is a DECISION, never a reflex (db-rules §6a): 'personal' ONLY for an individual
-- person's own artifacts (chats, DMs — almost nothing qualifies); org work product → 'internal';
-- scraped/derived/catalog content → 'public'. Defaulting to the lowest tier without a
-- personal-artifact justification is the documented anti-pattern that locked out whole orgs.
-- normalize legacy → canonical (guard each on IS NULL so re-runs are no-ops)
update <schema>.<table> set created_by = coalesce(created_by, <owner_col>) where created_by is null;
update <schema>.<table> set visibility = 'public' where <is_public_col> is true and visibility <> 'public';
update <schema>.<table> set deleted_at = <deleted_ts_or_now> where <is_deleted_col> is true and deleted_at is null;
-- backfill org: copy it from the parent, or name the owning organization explicitly per row set.
-- Never pick one from the is_personal flag (being deleted — organizations are equal, access-ladder law).
-- drop legacy updated_at trigger, attach the shared triggers
drop trigger if exists <legacy_updated_at_trigger> on <schema>.<table>;
drop trigger if exists _touch_row on <schema>.<table>;
drop trigger if exists _stamp_actor on <schema>.<table>;
create trigger _touch_row  before insert or update on <schema>.<table> for each row execute function platform._touch_row();
create trigger _stamp_actor before insert or update on <schema>.<table> for each row execute function platform._stamp_actor();

Verify 0 null org and 0 null created_by (where an owner existed) before continuing. Backend-written tables: ensure the engine writes created_by (service role doesn't stamp it — TOOLKIT.md §5).

Step 1.5 — Base FK constraints + organization_id NOT NULL (REQUIRED — nothing else adds these)

retrofit_entity adds the columns but NOT the FK constraints or the NOT-NULL, and the gate checks all four. Skip this and verify_canonical returns four hard FAILs — base_org_fk, base_created_by_fk, base_updated_by_fk, base_org_not_null (verified live 2026-07-05). Run after org is backfilled to 0 nulls:

alter table <schema>.<table> alter column organization_id set not null;
alter table <schema>.<table> add constraint <table>_org_fk foreign key (organization_id) references iam.organizations(id);
alter table <schema>.<table> add constraint <table>_created_by_fk foreign key (created_by) references auth.users(id);
alter table <schema>.<table> add constraint <table>_updated_by_fk foreign key (updated_by) references auth.users(id);

Idempotent guard for re-runs: wrap each in do $$ begin if not exists (select 1 from pg_constraint where conname='<table>_org_fk') then … end if; end $$; or check information_schema.table_constraints first. SET NOT NULL fails if ANY org is null — fix Step 1's backfill, never weaken this. (FK targets: organization_id → iam.organizations(id), created_by/updated_by → auth.users(id) — these exact targets are what the gate matches.)

Safety assert (put it at the end of the Steps 1–1.5 migration so the whole thing rolls back on any miss):

do $$ begin
  if exists (select 1 from <schema>.<table> where organization_id is null) then raise exception 'null organization_id remains'; end if;
  if not exists (select 1 from pg_trigger where tgrelid='<schema>.<table>'::regclass and tgname='_touch_row') then raise exception '_touch_row not attached'; end if;
  if not exists (select 1 from pg_trigger where tgrelid='<schema>.<table>'::regclass and tgname='_stamp_actor') then raise exception '_stamp_actor not attached'; end if;
end $$;

Step 2 — Register the entity (idempotent)

insert into platform.entity_types (token, schema_name, table_name, label, default_visibility, is_component, is_versioned, is_active)
select '<token>','<schema>','<table>','<Label>','<TIER>', <is_component:false|true>, <is_versioned:true|false>, true  -- <TIER>: same decision as Step 1 (db-rules §6a)
where not exists (select 1 from platform.entity_types where token='<token>');

If component, add the composition edge (required before apply_rls):

insert into platform.entity_relationships (child_type, parent_type, fk_column, kind)
select '<token>','<parent_token>','<fk_col>','composition'
where not exists (select 1 from platform.entity_relationships where child_type='<token>' and kind='composition');

(Optional kind='containment' edges add read-cascade for visibility>=internal — additive.)

Step 3 — Sharing registry (entity is user-shareable)

The registry lives in platform.shareable_resource_registry — it moved out of public in the 2026 reorg (verified live 2026-07-05; a public. insert errors with table-not-found).

insert into platform.shareable_resource_registry
  (resource_type, schema_name, table_name, id_column, owner_column, is_public_column, display_label, url_path_template, rls_uses_has_permission)
values ('<token>','<schema>','<table>','id','created_by','visibility','<Label>','/<path>/{id}',true);

resource_type MUST equal <token> (mismatch → grants silently ignored).

Step 4 — Canonical RLS (the only policy authority)

Inventory existing policies first (apply_rls drops them all); fold anything load-bearing into the standard model. Then:

select iam.apply_rls('<schema>','<table>','<token>','<entity|component|ledger|restricted|system>');

(entity requires created_by + organization_id present; component requires the composition edge; restricted is owner-or-super-admin-only with no has_access path (secret-bearing system tables); system requires a visibility column (catalog/shared-definition tables) — see Step 0. There is no join variant — TOOLKIT.md §2. A pure M2M join table (a_id+b_id, no lifecycle) is best collapsed into platform.associations (db-rules §3); if it must stay a table, policy it as component (composition edge) or hand-write org-gated policies like platform.associations — never invent a join variant.)

Step 5 — Versioning

  • Table already versions (history/audit today) → put it on the central system: attach the capture trigger.
  • Table does NOT version today → ASK the PM (the user) whether to add versioning at canonicalization. Do not silently add or skip.
-- when versioned: set is_versioned=true in entity_types AND attach the trigger (registration alone captures nothing)
create trigger _history after insert or update or delete on <schema>.<table>
for each row execute function platform._version_capture('<token>');

Confirm the current month's history.row_versions partition exists (capture starts only when _history is attached — pre-existing rows are NOT auto-backfilled). Backfilling old versions (retiring a per-feature *_versions table): history.row_versions is monthly RANGE-partitioned, so first pre-create a partition for EVERY month in the source's date range (min(created_at)..max) or the INSERT fails with a no-partition error (verified — note_versions spanned 7 months, only 2 partitions existed). Map row_id/version/occurred_at/actor_id; stash extra fields under reserved _* keys in row_data; verify count(history)=count(source). (Optional _gc → platform._gc_entity_associations('<token>') cleans association edges on delete.)

Step 6 — Replace the feature's bespoke subsystems with the platform satellites

If the feature has its OWN comments / associations-relationships / categories / activity-log / favorites-pins, migrate the rows into the platform tables keyed by (entity_type='<token>', entity_id) and deprecate the old tables (use db-deprecate-table). Behavior must be identical afterward — verify the UI shows the same comments/tags/relationships. (platform.comments, platform.associations, platform.categories, platform.activity_log, platform.user_entity_state — shapes in TOOLKIT.md §1.) Permissions/shares → migrate into iam.permissions (moved from public in the reorg; TOOLKIT.md §3).

Step 6.5 — Drop the legacy columns (reach zero-WARN)

In downtime, once consumers are repointed, drop user_id/owner_id, is_public, is_deleted, shared_with to clear the legacy WARNs. Each drop has couplings — check first (all bit us on notes):

  • Backfill with triggers OFF: wrap the is_deleted→deleted_at / is_public→visibility UPDATE in alter table … disable trigger user; … ; enable trigger user;, else _touch_row churns version/updated_at and the version/sync/ingest triggers fire on every touched row.
  • Functions/triggers that read the column (the silent killers) — use audit.table_impact first, then a text scan: select * from audit.table_impact('<schema>','<table>'); returns every dependent function with dependency (precise|text), currently_broken, and the exact referenced_columns[] it touches — the blast radius, purpose-built for this. THEN belt-and-suspenders with a raw scan of ALL functions (prokind in ('f','p') to skip aggregates) for BOTH (from|join|update)\s+<table> AND new\.<col>/old\.<col>, because a FROM/JOIN scan MISSES trigger functions that read NEW.<col> (notify/ingest/sync/version triggers) and SECURITY DEFINER functions aren't caught by tsc. Verified casualties of skipping this: _notify_auto_ingest_note (NEW.is_deleted → every note write failed silently) and get_user_dashboard_metrics (is_deleted → dashboard broke). Patch each to the canonical column before dropping.
  • RLS on OTHER tables: a child policy may reference this column via subquery (… WHERE notes.user_id = auth.uid()). DROP COLUMN fails 2BP01 and lists them — repoint each to created_by; never blind-CASCADE (it silently drops the policy).
  • Indexes: DROP COLUMN auto-drops indexes that include it. Recreate the useful composites on created_by (owner/sync/folder lookups) first.
  • Prove safe first: count(*) filter (where created_by is distinct from <owner_col>)=0, is_public/shared_with empty — then the drop loses nothing.

Step 7 — Verify (the two-gate acceptance)

There are two gates and you must clear BOTH. verify_canonical checks the table's own structure; canonical_certify also checks that no dependent function is broken — it is the "done" gate per db-rules FEATURE.md §11 (nothing is done until canonical_certify_ok is true).

select * from iam.verify_canonical('<schema>','<table>','<token>');    -- read EVERY row
select iam.verify_canonical_ok('<schema>','<table>','<token>');         -- gate 1: no structural FAIL
select * from iam.canonical_certify('<schema>','<table>','<token>');    -- blocking rows = FAIL/WARN + any broken dependent fn
select iam.canonical_certify_ok('<schema>','<table>','<token>');        -- gate 2 (THE done gate): true = done

Bar (verified live 2026-07-05): these must all reach PASS — base_org_not_null, base_org_fk, base_created_by_fk, base_updated_by_fk (Step 1.5), col_visibility, soft_delete, timestamps, plus the registration/RLS/policy checks. Skip Step 1.5 and you get four base_*_fk/not_null FAILs. The three legacy WARNs — legacy_owner_col (user_id/owner_id), legacy_is_public, legacy_is_deleted — clear only when those columns are dropped (Step 6.5). Full canonical = both gates true with zero FAIL + zero WARN, achievable in one pass when you do the drops (proven live on a probe + on notes+note_folders); if the drops must wait, the transition-state floor is zero FAIL + only those legacy WARNs. Don't report "canonical" with any FAIL or a col_visibility/soft_delete WARN showing, or with canonical_certify_ok=false.

Refresh the audit store so the table shows up certified in the platform-wide hit list (drives audit.summary; loud + non-blocking in release.sh):

-- run from a shell (session mode, never 6543): cd aidream && uv run python scripts/audit_refresh.py
select fails, warns, certified from audit.summary where token='<token>';

Then impersonate a normal user and confirm they still read their own rows (RLS didn't hide data):

select set_config('request.jwt.claims', json_build_object('sub','<a real user uuid>')::text, true);
select count(*) from <schema>.<table>;   -- expect their visible rows, not 0

Step 8 — Cross-repo finalize + document

db-change SOP: pnpm db-types → update all usages (new columns, .schema() if needed, RPC names) → pnpm sync-types (fix TS); python db/generate.py → update usages + package_integration.py → python db/detect_applied.py → python run.py clean boot. Ledger the migration. Update the feature's FEATURE.md + add a line to the Done log in ../common-docs/operations/db-changeover-board.md. Commit + push main on both repos.

NEVER

  • apply_rls before org is backfilled (0 nulls) or before entity_types (+ composition edge for components) exists.
  • Skip Step 1.5 — retrofit_entity adds the base columns but NOT the FK constraints or organization_id NOT NULL; without them the table carries four permanent base_* FAILs no matter how clean it looks.
  • Trust verify_canonical_ok alone — WARNs are unfinished canonicalization, and it does NOT check dependent functions. iam.canonical_certify_ok=true is the only "done" signal (db-rules FEATURE.md §11).
  • Assume is_versioned=true captures history — it doesn't without the _history trigger.
  • Leave the feature reading its old comments/associations table after migrating the rows — repoint the code, then deprecate the table.
  • Change behavior. Canonicalization preserves what the user sees; if anything differs, it's a bug.
  • Touch out-of-scope litter as if it were legacy tagging: sch_* (scheduler), wf_*/workflow, code_*, wc_* — their project_id/task_id columns are real FKs, NOT association litter to migrate. Leave them.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Admin debug system wiring for the floating AdminIndicator's live debug data and Copy Full Context. Use when adding debug visibility to a route or feature, wiring useDebugContext or the debug panel, capturing console errors, or enabling the copy-context workflow for a page.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Compact two-icon Copy / Copy-for-AI controls (components/agent-copy). Use when adding copy buttons to a row, card, list, or record; merging duplicate Copy/JSON/AI controls; continuing the copy rollout; or writing a Copy-for-AI payload. NOT for markdown content actions (use rich-document-actions).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Disclosing a surface's existing fixed AI jobs in the shell's top Agents menu. Use when a page, panel, overlay, or window already runs a mandate behind a button, assist, automatic action, or mode; when the agent-disclosure guard names a file; or during a surface check.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

The watch-fix-rerun method for making an existing platform agent or automated process efficient and correct: baseline its ledger, run one unit yourself, fix the class behind every wasted call, rerun, record. Use when asked to improve, optimize, watch, or 'make efficient' an agent, a sandbox session, a sync job, or any recurring automated process.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Redux state for agent execution and firing agent shortcuts. Use when editing features/agents/redux/, building agent UI, creating a conversation, touching assembleRequest or NDJSON stream state, wiring agent-state selectors, adding a per-conversation capability, or triggering a shortcut from a button, menu, or code (useShortcutTrigger, launchAgentExecution).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

The provision for a call site: the exhaustive menu of values that place in the code can realistically produce. Use before creating or fixing a mandate or agent that reasons about more than its own input, when deciding whether an agent can answer at all with what it is sent, or when its output 'looks right' but nobody checked it could be. NOT for building the agent (use create-agent).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

armanisadeghi のスキルをすべて見る

このスキルの問題を報告する